pax_global_header00006660000000000000000000000064135414360250014515gustar00rootroot0000000000000052 comment=8a58df6848de70597aa503396e034698ff8d82d4 yash-2.49/000077500000000000000000000000001354143602500124175ustar00rootroot00000000000000yash-2.49/.gitignore000066400000000000000000000006611354143602500144120ustar00rootroot00000000000000*.d *.o Makefile TAGS tags cscope.out *.1 *.html *.xhtml *.xml index.txt yash.txt /yash /makesignum /signum.h /configm.h /config.h /config.log /config.status /builtins/builtins.a /lineedit/lineedit.a /lineedit/commands.in /po/stamp-po /po/yash.pot /po/*.mo /po/*.gmo /po/en@quot.po /po/en@quot.ih /po/en@boldquot.po /po/en@boldquot.ih /share/config /tests/checkfg /tests/ptwrap /tests/resetsig /tests/summary.log /tests/*.trs yash-2.49/.travis.yml000066400000000000000000000003301354143602500145240ustar00rootroot00000000000000sudo: false language: c compiler: - gcc - clang addons: apt: packages: - ed script: export CC="$CC -std=c99" PATH="$HOME/bin:$PATH" && ./configure -d && make -k test after_failure: cat tests/summary.log yash-2.49/COPYING000066400000000000000000000431031354143602500134530ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. yash-2.49/INSTALL000066400000000000000000000335211354143602500134540ustar00rootroot00000000000000====================================================================== Yash Build and Installation Guide ====================================================================== A one-liner for experts: ./configure && make && sudo make install ---------------------------------------------------------------------- Contents: 1. What you need to build yash 2. Configuration 3. Building and testing 4. Installation and uninstallation 5. More configuration options ---------------------------------------------------------------------- 1. What You Need to Build Yash Yash is a program written in C99 (ISO/IEC 9899:1999), an extended version of the C programming language, so you need a C99 compiler to build it. If you do not have a C99 compiler installed on your system, install one first. If you have a C99 compiler installed as the `c99' command, it will suffice. Or if you have a recent version of the `gcc' command installed from the GNU Compiler Collection, it will also do. If you have a C99 compiler other than the `c99' or `gcc' command and want to use that, you have to specify the compiler in configuration. The build process of yash is automated using the `make' command, so you need the `make' command installed in addition to the compiler. It is recommended to use a version of `make' that conforms to POSIX. GNU Make is confirmed to work, and most of other versions will also do. Yash is based on API specified in the POSIX standard (IEEE Std 1003.1, 2008 Edition). If your system does not support the POSIX API, yash may not be able to be built or work as expected. Yash uses the `gettext' function to support localized messages. On recent versions of GNU/Linux and Solaris, the `gettext' function is provided in the standard C library, so you do not need any extra library. On some other systems, the `gettext' function is provided in an extra library called `libintl'. If you do not have the `libintl' library installed on your system, first install the library or disable the internationalization support in configuration to build yash without it. Yash uses the `curses' library to support powerful command line editing. If you do not have the `curses' library installed on your system, first install it or disable command line editing in configuration to build yash without it. ---------------------------------------------------------------------- 2. Configuration The configuration process produces some files that are used in the main build process. There files contain specific information about which features are supported on your system, where yash will be installed, etc. The configuration is done by the shell script named `configure'. To start the configuration, change the working directory of your shell to the directory containing the `configure' script using the `cd' command and invoke `sh configure'. You can specify some options and/or variables when invoking the `configure' script in order to specify: * the compiler, the archiver, etc. that are used in the build * features that will be supported by the resultant executable * directories where executable and other files are installed. For example, to configure with the command line editing feature disabled, invoke the script as `sh configure --disable-lineedit'. To use the `cc' command as the compiler during the build, `sh configure CC=cc' The rest of this section describes the options and variables that can be specified. The following options can be specified to enable or disable specific features of yash. By default, yash is built with all features enabled. --enable-array --disable-array If disabled, the `array' built-in command is not available. Note that array variables are available regardless of this option. --enable-dirstack --disable-dirstack If disabled, the `dirs', `pushd', and `popd' built-in commands are not available. --enable-double-bracket --disable-double-bracket If disabled, the double-bracket command (the [[ ... ]] syntax) is not available. When this feature is enabled, the `test' built-in must also be enabled. --enable-help --disable-help If disabled, the `help' built-in command is not available. --enable-history --disable-history If disabled, the `fc' and `history' built-in commands are not available. --enable-lineedit --disable-lineedit If disabled, command line editing for the interactive shell is not available. When this feature is enabled, the history feature must also be enabled. --enable-printf --disable-printf If disabled, the `printf' and `echo' built-in commands are not available. --enable-socket --disable-socket If disabled, socket redirection is not available. To enable this feature, your system have to support sockets. --enable-test --disable-test If disabled, the `test' and `[' built-in commands are not available. --enable-ulimit --disable-ulimit If disabled, the `ulimit' built-in command is not available. To enable this feature, your system must support the `getrlimit' and `setrlimit' functions in the standard C library. The following option specifies the behavior of yash: --default-loadpath=... When yash is invoked, the $YASH_LOADPATH variable is initialized to this value. (default: /yash) The following options specify where files are installed: --prefix=... The basic installation path prefix. (default: /usr/local) --exec-prefix=... The basic installation path prefix for binaries. (default: ) --bindir=... The directory where the main executable binary is installed. (default: /bin) --datarootdir=... The basic installation path prefix for files other than the main executable binary. (default: /share) --datadir=... The directory where auxiliary script files are installed. (default: ) --localedir=... The directory where localized message data are installed. (default: /locale) --mandir=... The directory where roff-format manual pages are installed. (default: /man) --docdir=... The directory where non-roff-format manual pages are installed. (default: /doc/yash) --htmldir=... The directory where HTML manual pages are installed. (default: ) The following variables can be used to specify commands and their options used during the build: CC=... This specifies the compiler/linker command used during the build. CFLAGS=... This specifies the compiler option used in compiling (but not in linking). This overrides the options used in the default configuration. CADDS=... This specifies the compiler option used in compiling (but not in linking) in addition to the CFLAGS variable above. You can use CADDS to add compiler options to the default ones. LDFLAGS=... This specifies the linker option used in linking (but not in compiling). This overrides the options used in the default configuration. This variable does not include options that specify linked libraries (see LDLIBS below). LDADDS=... This specifies the linker option used in linking (but not in compiling) in addition to the LDFLAGS variable above. You can use LDADDS to add linker options to the default ones. LDLIBS=... This specifies the linker option that specify libraries linked to the executable binary. This overrides the options automatically detected by the configuration process. AR=... This specifies the archiver command used during the build. ARFLAGS=... This specifies the archiver option used in making a binary archive. This overrides the options used in the default configuration. LINGUAS=... This specifies the names of locales for which localized message data are installed. To specify more than one locale, separate names by spaces. (All space-separated names must be given at once in one variable value. The whole variable value should be properly quoted when invoking the `configure' script.) INSTALL=... This specifies the installer command used in installation. ---------------------------------------------------------------------- 3. Building and Testing Once the configuration is done, to build yash, invoke the `make' command from your shell. The `make' command will automatically invoke the compiler and linker in a proper order to produce the executable binary of yash. After yash is built, you can test the functionality of it by invoking `make test'. (Actually, you can directly invoke `make test' before `make' because the test process automatically builds yash if it is not yet built.) ---------------------------------------------------------------------- 4. Installation and Uninstallation After yash is built, to install yash in your system, invoke `make install'. This will install the main executable binary of yash and all auxiliary files used by yash. If the installation directories are not specified in the configuration, the files are installed into the following directories by default: Main executable binary: /usr/local/bin Auxiliary shell scripts: /usr/local/share/yash Localization data: /usr/local/share/locale Roff-format manual: /usr/local/share/man HTML-format manual: /usr/local/share/doc/yash In most systems, a special permission is required to install files into these directories. Use the `sudo' or other proper command to obtain the permission or you may want to install into other directories by specifying the directories in the configuration. Instead of `make install', you can use `make install-binary' to install the main binary only or `make install-data' to install the other data files only. Instead of `make install' or `make install-binary', you can use `make install-strip' or `make install-binary-strip', respectively, to remove debugging information from the binary during installation. This makes the installed binary size smaller. To uninstall yash, invoke `make uninstall'. You can instead use `make uninstall-binary' or `make uninstall-data' to uninstall the main binary or the other data files only, respectively. ---------------------------------------------------------------------- 5. More Configuration Options After invoking the `configure' script, you can manually edit the `config.h' file to customize yash more. These options cannot be configured by the `configure' script because they are not so interesting for most people to customize. The options can be set by defining macros in the `config.h' file as described below. Some of the options are Boolean options. To enable a Boolean option, define the corresponding macro as a non-zero integer value. To disable a Boolean option, define the macro as zero or leave it undefined. For non-Boolean options, the default value is shown to the right of the macro name. #define ALIAS_LIST_MAX 30 This macro must be defined as a positive integer. This macro specifies the maximum number of aliases that can be expanded recursively. #define DOUBLE_DIVISION_BY_ZERO_ERROR 1 /* Boolean option */ If this macro is set to a non-zero, division by zero in floating-point arithmetic is treated as an error. Otherwise, division by zero is assumed to return a valid result (like infinity). #define FG_DONT_SAVE_TERMINAL 1 /* Boolean option */ When a program that changes the terminal settings is invoked in the background and later continued in the foreground by the `fg' built-in command, it may leave the terminal in the wrong settings. By default, the `fg' command works around this problem by saving the terminal settings before continuing the program and restoring the settings after the program has finished. Defining this macro as a non-zero disables this workaround. #define FIXED_SIGNAL_AS_ERROR 1 /* Boolean option */ As specified in the POSIX standard, if signal handlers that ignore signals are inherited from the invoker process to a shell process, the signal handlers cannot be removed by the `trap' built-in command if the shell is non-interactive. This macro specifies the behavior of the `trap' command in such a case. If this macro is set to a non-zero, the command results in an error. Otherwise, the command returns the exit status of success without any error message. Note that the signal handlers are not removed regardless of this macro. #define FORMAT_INDENT_WIDTH 3 This macro must be defined as a non-negative integer. This macro specifies the number of spaces used to indent commands that are formated and printed by the shell. #define MAX_HISTSIZE 1000000 This macro specifies the maximum size of history. The value must not over (INT_MAX / 10). #define HISTORY_MIN_MAX_NUMBER 100000 This macro specifies the number of history entry at which the number wraps back to 1. (If there are very many history entries, the entry number may wrap at a number larger than this value.) The value must be a power of 10 that is not less than 32768. #define DEFAULT_HISTSIZE 500 This macro specifies the default history size. The value must be 128 or larger. #define LIST_AMBIGUOUS_OPTIONS 1 /* Boolean option */ When an option specified in invocation of the shell or a built-in command is ambiguous, a list of option names that match the specified ambiguous name is printed if this macro is defined as a non-zero. #define MAX_PREDICTION_SAMPLE 10000 This macro must be defined as a non-negative integer. This macro specifies the maximum number of history entries that are considered in computing a command line prediction candidate. #define SHELLFDMINMAX 100 This macro must be defined as an integer that is not less than 10. When the shell opens a file that is not directly used by user commands, the file descriptor for the file is chosen from integers that is not less than the value of this macro. #define YASH_DISABLE_SUPERUSER 1 /* Boolean option */ If this macro is set to a non-zero, the user whose user ID is zero is not treated as a superuser, who is considered to have special privilege about file access. yash-2.49/INSTALL.ja000066400000000000000000000443601354143602500140500ustar00rootroot00000000000000====================================================================== Yash ビルド・インストールガイド ====================================================================== 分かる人向けワンライナー: ./configure && make && sudo make install ---------------------------------------------------------------------- 目次: 1. Yash をビルドするのに必要なもの 2. コンフィギュレーション 3. ビルドとテスト 4. インストールとアンインストール 5. 更なるコンフィギュレーションオプション ---------------------------------------------------------------------- 1. Yash をビルドするのに必要なもの Yash は C99 (ISO/IEC 9899:1999, 拡張版 C 言語) で書かれたプログラムな ので、ビルドするには C99 のコンパイラが必要です。お使いのシステムに C99 のコンパイラがない場合は、まずそれをインストールしてください。C99 のコンパイラが `c99' コマンドとしてインストールしてある場合は、それで 十分です。また比較的新しい GNU Compiler Collection の `gcc' コマンドが インストールしてある場合は、それが使えます。これら以外の C99 のコンパ イラを使用したい場合は、コンフィギュレーションでそれを指定してください。 Yash のビルドは `make' コマンドによって自動化されていますので、コンパ イラに加えて `make' コマンドが必要です。POSIX 規格に準拠した `make' コ マンドを使用することを推奨します。GNU Make で正しくビルドできることを 確認済みです。他の `make' コマンドでも多くの場合は問題ありません。 Yash は POSIX 規格 (IEEE Std 1003.1, 2008 Edition) で定義された API を 使用しています。お使いのシステムが POSIX に対応していない場合、ビルド に失敗したり正しく動作しなかったりする可能性があります。 Yash は各言語に翻訳されたメッセージを表示するために `gettext' 関数を使 用します。GNU/Linux および Solaris の最近のバージョンでは、`gettext' 関数は標準 C ライブラリに組み込まれているので、そのままで問題ありませ ん。他のシステムでは、`gettext' 関数は `libintl' という外部ライブラリ で提供されます。お使いのシステムに `libintl' ライブラリがインストール されていない場合は、まずそれをインストールするか、またはコンフィギュレ ーションで国際化対応を無効化してください。 Yash はコマンドライン編集機能を実現するために `curses' ライブラリを使 用します。お使いのシステムに `curses' ライブラリがインストールされてい ない場合は、まずそれをインストールするか、またはコンフィギュレーション でコマンドライン編集機能を無効化してください。 ---------------------------------------------------------------------- 2. コンフィギュレーション コンフィギュレーションではビルドで使用されるいくつかのファイルが生成さ れます。これらのファイルには、システムで利用可能な機能の種類や yash を どこにインストールするかなどといった情報が入っています。 コンフィギュレーションは `configure' という名前のシェルスクリプトによ って行われます。コンフィギュレーションを実行するには、`cd' コマンドで お使いのシェルの作業ディレクトリを `configure' スクリプトがあるディレ クトリに変更した後、`sh configure' と入力してください。 コンフィギュレーションにおいて、`configure' スクリプトを起動する際にオ プションや変数を与えることができます。これにより、 * ビルドで使用するコンパイラやアーカイバ * ビルドした yash で使用可能な機能 * yash 本体や関連ファイルをインストールするディレクトリ 等を指定することができます。 例えば、コマンドライン編集機能を無効化してビルドするには、スクリプトを `sh configure --disable-lineedit' と入力して実行します。また `cc' コマ ンドをコンパイラとしてビルドで使用するには、`sh configure CC=cc' とし ます。 以下に、コンフィギュレーションで指定可能なオプション・変数を挙げます。 以下は、yash の特定の機能を有効化・無効化するオプションです。デフォル トでは、yash はすべての機能が有効な状態でビルドされます。 --enable-array --disable-array 配列を処理するための `array' 組込みコマンドを有効・無効にしま す。この機能を無効にしても、配列変数そのものは常にサポートされ ます。 --enable-dirstack --disable-dirstack ディレクトリスタックおよびそれを扱うための `dirs', `pushd', `popd' 組込みコマンドを有効・無効にします。 --enable-double-bracket --disable-double-bracket 二重ブラケットコマンド ([[ ... ]] 構文) を有効・無効にします。 この機能を有効にするには `test' 組込みコマンドも有効にしなければ なりません。 --enable-help --disable-help `help' 組込みコマンドを有効・無効にします。 --enable-history --disable-history コマンド履歴を扱うための `fc', `history' 組込みコマンドを有効 ・無効にします。 --enable-lineedit --disable-lineedit 対話シェルのための行編集機能を有効・無効にします。この機能を有 効にするにはコマンド履歴機能も有効にしなければなりません。 --enable-printf --disable-printf `printf', `echo' 組込みコマンドを有効・無効にします。 --enable-socket --disable-socket ソケットリダイレクトを有効・無効にします。この機能を有効にするには、 お使いのシステムがソケットをサポートしている必要があります。 --enable-test --disable-test `test', `[' 組込みコマンドを有効・無効にします。 --enable-ulimit --disable-ulimit `ulimit' 組込みコマンドを有効・無効にします。この機能を有効にする には、お使いのシステムの標準 C ライブラリが `getrlimit' および `setrlimit' 関数をサポートしている必要があります。 以下のオプションは yash の動作を指定します: --default-loadpath=... Yash を起動したとき、$YASH_LOADPATH 変数をこの値に初期化します。 (デフォルト: /yash) 以下のオプションはファイルのインストール先を指定します: --prefix=... インストール先の基本先頭パス (デフォルト: /usr/local) --exec-prefix=... 実行可能バイナリのインストール先の基本先頭パス (デフォルト: ) --bindir=... 実行可能バイナリのインストール先ディレクトリ (デフォルト: /bin) --datarootdir=... 実行可能バイナリ以外のファイルのインストール先の基本先頭パス (デフォルト: /share) --datadir=... 補助スクリプトのインストール先 (デフォルト: ) --localedir=... 地域化対応用メッセージデータファイルのインストール先 (デフォルト: /locale) --mandir=... Roff 形式のマニュアルのインストール先 (デフォルト: /man) --docdir=... Roff 形式以外のマニュアルのインストール先 (デフォルト: /doc/yash) --htmldir=... HTML 形式のマニュアルのインストール先 (デフォルト: ) 以下の変数はビルドで使用するコマンドとそのオプションを指定するのに使え ます: CC=... ビルドで使用するコンパイラ兼リンカコマンドを指定します。 CFLAGS=... コンパイル時にコンパイラに渡すオプションを指定します。この変数はコ ンフィギュレーションで自動的に設定されるオプションを上書きします。 CADDS=... コンパイル時にコンパイラに渡す追加のオプションを指定します。CFLAGS で指定されるデフォルトのオプションのほかにオプションを追加する際に この変数を指定してください。 LDFLAGS=... リンク時にリンカに渡すオプションを指定します。この変数はコンフィ ギュレーションで自動的に設定されるオプションを上書きします。この変 数にはリンクするライブラリを指定するオプションは含まれません。(下 記 LDLIBS 参照) LDADDS=... リンク時にリンカに渡す追加のオプションを指定します。LDFLAGS で指定 されるデフォルトのオプションのほかにオプションを追加する際にこの変 数を指定してください。 LDLIBS=... 実行可能バイナリとリンクさせるライブラリを指定するためのリンカオプ ションを指定します。この変数はコンフィギュレーションで自動的に設定 されるオプションを上書きします。 AR=... ビルドで使用するアーカイバコマンドを指定します。 ARFLAGS=... バイナリアーカイブを作成する際にアーカイバに渡すオプションを指定し ます。この変数はコンフィギュレーションで自動的に設定されるオプショ ンを上書きします。 LINGUAS=... 地域化対応用のメッセージデータをインストールするロケールを指定しま す。複数のロケールを指定するには、ロケール名を空白で区切ってくださ い。(全てのロケール名を一つの LINGUAS 変数で一度に指定する必要があ ります。シェルから `configure' スクリプトを起動する際に変数の値全 体を適切にクォートしてください。) INSTALL=... インストール時に使用するインストーラコマンドを指定します。 ---------------------------------------------------------------------- 3. ビルドとテスト コンフィギュレーションができたら、シェルから `make' コマンドを実行して yash をビルドしてください。`make' コマンドがコンパイラやリンカを適切な 順序で起動して、yash の実行可能バイナリを生成します。 Yash のビルドが済んだら、yash が正しく機能するかどうか `make test' を 実行することでテストできます。(実際には、`make' をせずにいきなり `make test' をしても構いません。テスト前に自動的にビルドが行われます。) ---------------------------------------------------------------------- 4. インストールとアンインストール ビルドができたら、`make install' を実行することで yash をインストール できます。このコマンドを実行することで、yash の実行可能バイナリと yash が使用する補助ファイルがすべてインストールされます。コンフィギュレー ションでインストール先を指定しなかった場合は、以下のデフォルトディレク トリにインストールされます。 実行可能バイナリ: /usr/local/bin 補助シェルスクリプト: /usr/local/share/yash 地域化対応用データ: /usr/local/share/locale Roff 形式マニュアル: /usr/local/share/man HTML 形式マニュアル: /usr/local/share/doc/yash ほとんどのシステムでは、これらのディレクトリにファイルをインストールす るには特別な権限が必要となります。権限を取得するために `sudo' 等のコマ ンドを使用してください。また代わりに別のインストール先ディレクトリをコ ンフィギュレーションで指定することもできます。 `make install' の代わりに、`make install-binary' を実行して実行可能バ イナリのみをインストールすることもできます。また `make install-data' で他の補助ファイルのみをインストールすることもできます。 `make install' または `make install-binary' の代わりに、 `make install-strip' または `make install-binary-strip' をそれぞれ使用 することもできます。これらはインストール中にバイナリからデバッグ用情報 を削除することでファイルサイズを小さくします。 Yash をアンインストールするには、`make uninstall' を実行してください。 `make uninstall-binary' および `make uninstall-data' を使うと実行可能 バイナリおよびその他の補助ファイルのみをそれぞれアンインストールするこ ともできます。 ---------------------------------------------------------------------- 5. 更なるコンフィギュレーションオプション `configure' スクリプトを実行することでコンフィギュレーションを行った後、 `config.h' ファイルを直接編集することでさらに yash をカスタマイズする ことができます。ここで述べるオプションは多くの人にとっては関心のないこ となので、`configure' スクリプトで設定できるようにはなっていません。 オプションは、`config.h' ファイル内に所定のマクロを定義することで設定 できます。オプションは、ブーリアンオプションとそれ以外のオプションとに 分かれます。ブーリアンオプションは、マクロの値を 0 以外の整数値として 定義すると有効になり、0 として定義するか未定義のままにすると無効になり ます。非ブーリアンオプションについては、デフォルトの値をマクロ名の横に 示してあります。 #define ALIAS_LIST_MAX 30 このマクロは正の整数値として定義する必要があります。 このマクロは、一度に再帰的に展開できるエイリアスの数を指定します。 #define DOUBLE_DIVISION_BY_ZERO_ERROR 1 /* ブーリアンオプション */ このマクロを非 0 値に設定すると、小数のゼロによる除算をエラーとします。 デフォルトでは、ゼロによる除算は有効な結果 (例えば無限大) を返すと仮定 されます。 #define FG_DONT_SAVE_TERMINAL 1 /* ブーリアンオプション */ 端末の設定を操作するプログラムがバックグラウンドで起動され、後で `fg' 組込みコマンドで再開された場合、端末の設定がおかしなまま残ることがあり ます。そのためデフォルトでは `fg' 組込みコマンドはプログラムを再開させ る前に端末の設定を保存し、後で設定を元に戻します。しかしこのマクロが非 0 値に設定してあると、`fg' 組込みコマンドはこのような回避策を取りませ ん。 #define FIXED_SIGNAL_AS_ERROR 1 /* ブーリアンオプション */ 非対話モードのシェルが起動する際に元からシグナルハンドラが「無視」に設 定されていた場合、`trap' 組込みコマンドで後からそれを解除することはで きないと POSIX 規格で定められています。このオプションは、このような場 合における `trap' コマンドの動作を指定します。このマクロが非 0 値に設 定されている場合は、コマンドはエラーメッセージを出力し、そうでない場合 は、コマンドは何もエラーが無かったかのように振る舞います。なお、いづれ の場合も、当該シグナルハンドラは解除されません。 #define FORMAT_INDENT_WIDTH 3 このマクロは 0 以上の整数値として定義する必要があります。 このマクロは、シェルがコマンドを整形して出力する際のインデントの幅を指 定します。 #define MAX_HISTSIZE 1000000 このマクロは (INT_MAX / 10) を超えない正の整数値として定義する必要があ ります。 このマクロは履歴項目の最大個数を指定します。 #define HISTORY_MIN_MAX_NUMBER 100000 このマクロは 32768 以上の 10 の冪として定義する必要があります。 このマクロは、履歴の番号が 1 に戻る直前の履歴の番号を指定します。(履歴 が非常にたくさんある場合はこのマクロの値以上の番号で 1 に戻る場合もあ ります。) #define DEFAULT_HISTSIZE 500 このマクロは 128 以上の整数として定義する必要があります。 このマクロはデフォルトの履歴サイズを指定します。 #define LIST_AMBIGUOUS_OPTIONS 1 /* ブーリアンオプション */ このマクロが非 0 値に設定してある場合、シェルの起動または組込みコマン ドの呼び出しにおいて指定されたオプションが曖昧な時、そのオプション名に マッチするオプション名のリストを出力します。 #define MAX_PREDICTION_SAMPLE 10000 このマクロは 0 以上の整数値として定義する必要があります。 このマクロは、コマンドライン推定の候補を算出するために考慮されるコマン ド履歴の最大個数を指定します。 #define SHELLFDMINMAX 100 このマクロの値は 10 以上の整数でなければなりません。この値は、シェルが 内部で使用するファイルディスクリプタの下限を指定します。 #define YASH_DISABLE_SUPERUSER 1 /* ブーリアンオプション */ このマクロを非 0 値に設定すると、ユーザ ID が 0 のユーザを管理者権限を 持つユーザとして特別扱いしないようにします。デフォルトでは、UID が 0 のユーザはアクセス権限のチェックなどにおいて他のユーザとは異なる扱いを 受けます。 yash-2.49/Makefile.in000066400000000000000000000257721354143602500145010ustar00rootroot00000000000000# Makefile.in for yash: yet another shell # (C) 2007-2015 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # NOTE: In this Makefile it is assumed that the make implementation allows the # use of hyphens in target names. This means that there may be a strictly # POSIX-conforming implementation of make that rejects this Makefile. I have # never seen such an implementation but if you know of one please let me know. .POSIX: .SUFFIXES: .c .h .d .o .a @MAKE_SHELL@ topdir = . CC = @CC@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ LDFLAGS = @LDFLAGS@ LDLIBS = @LDLIBS@ INSTALL = @INSTALL@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_DIR = @INSTALL_DIR@ ARCHIVER = @ARCHIVER@ DIRS = @DIRS@ SOURCES = alias.c arith.c builtin.c exec.c expand.c hashtable.c history.c input.c job.c mail.c makesignum.c option.c parser.c path.c plist.c redir.c sig.c strbuf.c util.c variable.c xfnmatch.c xgetopt.c yash.c HEADERS = alias.h arith.h builtin.h common.h exec.h expand.h hashtable.h history.h input.h job.h mail.h option.h parser.h path.h plist.h redir.h refcount.h sig.h siglist.h strbuf.h util.h variable.h xfnmatch.h xgetopt.h yash.h MAIN_OBJS = alias.o arith.o builtin.o exec.o expand.o hashtable.o input.o job.o mail.o option.o parser.o path.o plist.o redir.o sig.o strbuf.o util.o variable.o xfnmatch.o xgetopt.o yash.o HISTORY_OBJS = history.o BUILTINS_ARCHIVE = builtins/builtins.a LINEEDIT_ARCHIVE = lineedit/lineedit.a OBJS = @OBJS@ TARGET = @TARGET@ VERSION = @VERSION@ COPYRIGHT = @COPYRIGHT@ BYPRODUCTS = makesignum.o makesignum signum.h configm.h *.dSYM DESTDIR = prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datarootdir = @datarootdir@ datadir = @datadir@ yashdatadir = $(datadir)/$(TARGET) localedir = @localedir@ mandir = @mandir@ docdir = @docdir@ htmldir = @htmldir@ default_loadpath = @default_loadpath@ enable_nls = @enable_nls@ all: $(TARGET) share/config tester mofiles docs .c.o: @rm -f $@ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< $(TARGET): $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) $(BUILTINS_ARCHIVE): _PHONY @+(cd builtins && $(MAKE)) $(LINEEDIT_ARCHIVE): _PHONY @+(cd lineedit && $(MAKE)) makesignum: $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $@.c $(LDLIBS) sig.o: signum.h signum.h: makesignum ./makesignum > $@ variable.o yash.o: configm.h configm.h: Makefile -@printf 'creating %s...' '$@' @{ printf '/* $@: created by Makefile */\n'; \ printf '#ifndef YASH_CONFIGM_H\n'; \ printf '#define YASH_CONFIGM_H\n'; \ printf '#define PACKAGE_NAME "$(TARGET)"\n'; \ printf '#define PACKAGE_VERSION "$(VERSION)"\n'; \ printf '#define PACKAGE_COPYRIGHT "$(COPYRIGHT)"\n'; \ printf '#define YASH_DATADIR "$(yashdatadir)"\n'; \ printf '#define LOCALEDIR "$(localedir)"\n'; \ printf '#define DEFAULT_LOADPATH "$(default_loadpath)"\n'; \ printf '#endif\n'; \ } >$@ -@echo done share/config: Makefile -@printf 'creating %s...' '$@' @{ printf '# $@: created by Makefile\n'; \ printf "prefix='%s'\n" '$(prefix)'; \ printf "exec_prefix='%s'\n" '$(exec_prefix)'; \ printf "bindir='%s'\n" '$(bindir)'; \ printf "datarootdir='%s'\n" '$(datarootdir)'; \ printf "datadir='%s'\n" '$(datadir)'; \ printf "yashdatadir='%s'\n" '$(yashdatadir)'; \ printf "localedir='%s'\n" '$(localedir)'; \ printf "mandir='%s'\n" '$(mandir)'; \ printf "docdir='%s'\n" '$(docdir)'; \ printf "htmldir='%s'\n" '$(htmldir)'; \ } >$@ -@echo done test tests check: _PHONY $(TARGET) @+(cd tests && $(MAKE)) tester: _PHONY @+(cd tests && $(MAKE) $@) mofiles: _PHONY @+(cd po && $(MAKE)) docs: -@+(cd doc && $(MAKE)) man html: -@+(cd doc && $(MAKE) $@-rec) INSTALLBINDIRS = $(DESTDIR)$(bindir) INSTALLDATADIRS = $(DESTDIR)$(yashdatadir) $(DESTDIR)$(yashdatadir)/completion $(DESTDIR)$(yashdatadir)/initialization $(DESTDIR)$(mandir) INSTALLDIRS = $(INSTALLBINDIRS) $(INSTALLDATADIRS) $(DESTDIR)$(htmldir) install: install-binary install-data install-strip: install-binary-strip install-data install-binary: $(TARGET) installdirs-binary $(INSTALL_PROGRAM) $(TARGET) $(DESTDIR)$(bindir)/$(TARGET) install-binary-strip: installdirs-binary @+$(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install-binary install-data: share/config installdirs-data-main @(cd share && find . -type f) | while read -r file; do \ echo $(INSTALL_DATA) share/$$file $(DESTDIR)$(yashdatadir)/$$file || true; \ $(INSTALL_DATA) share/$$file $(DESTDIR)$(yashdatadir)/$$file; \ done @+if $(enable_nls); then (cd po && $(MAKE) $@); fi @+(cd doc && $(MAKE) install-rec) install-html: @+(cd doc && $(MAKE) $@-rec) installdirs: installdirs-binary installdirs-data installdirs-binary: $(INSTALLBINDIRS) installdirs-data: installdirs-data-main @+if $(enable_nls); then (cd po && $(MAKE) $@); fi @+(cd doc && $(MAKE) installdirs-rec) installdirs-data-main: $(INSTALLDATADIRS) installdirs-html: $(DESTDIR)$(htmldir) $(INSTALLDIRS): $(INSTALL_DIR) $@ uninstall: uninstall-binary uninstall-data uninstall-binary: rm -f $(DESTDIR)$(bindir)/$(TARGET) uninstall-data: @(cd share && find . -type f) | while read -r file; do \ echo rm -f $(DESTDIR)$(yashdatadir)/$$file || true; \ rm -f $(DESTDIR)$(yashdatadir)/$$file; \ done rm -f $(DESTDIR)$(yashdatadir)/config -rmdir $(DESTDIR)$(yashdatadir)/completion -rmdir $(DESTDIR)$(yashdatadir)/initialization -rmdir $(DESTDIR)$(yashdatadir) @+if $(enable_nls); then (cd po && $(MAKE) $@); fi @+(cd doc && $(MAKE) $@-rec) DISTDIR = $(TARGET)-$(VERSION) DISTS = $(DISTDIR).tar $(DISTDIR).tar.Z $(DISTDIR).tar.gz $(DISTDIR).tar.bz2 $(DISTDIR).tar.xz $(DISTDIR).shar $(DISTDIR).shar.gz $(DISTDIR).zip ALL_DIST_TARGETS = dist-tarZ dist-gzip dist-bzip2 dist-xz dist-shar dist-zip DIST_TARGETS = dist-gzip dist-xz RM_DISTDIR = rm -rf $(DISTDIR) $(DISTDIR).tar $(DISTDIR): _PHONY @+(cd po && $(MAKE) update-po) # must be done first rm -fr $@ mkdir -m 755 $@ @+umask 022; \ for d in $(DIRS); do \ (cd $$d && $(MAKE) DISTTARGETDIR=$@/$$d copy-distfiles) || exit; \ done @find share -type d | while read -r dir; do \ echo mkdir -m 755 $@/$$dir || true; \ mkdir -m 755 $@/$$dir; \ done @find share -type f | (umask 022; while read -r file; do \ echo cp $$file $@/$$file || true; \ cp $$file $@/$$file; \ done) rm -f $@/share/config find $@ | xargs touch -c -r $@ # Only pax and compress conform to POSIX. dist: @+$(MAKE) RM_DISTDIR=: $(DIST_TARGETS) $(RM_DISTDIR) dist-all: @+$(MAKE) DIST_TARGETS='$(ALL_DIST_TARGETS)' dist dist-tarZ: $(DISTDIR).tar.Z $(RM_DISTDIR) dist-gzip: $(DISTDIR).tar.gz $(RM_DISTDIR) dist-bzip2: $(DISTDIR).tar.bz2 $(RM_DISTDIR) dist-xz: $(DISTDIR).tar.xz $(RM_DISTDIR) dist-shar: $(DISTDIR).shar.gz $(RM_DISTDIR) dist-zip: $(DISTDIR).zip $(RM_DISTDIR) $(DISTDIR).tar: $(DISTDIR) $(ARCHIVER) $@ $(DISTDIR) $(DISTDIR).tar.Z: $(DISTDIR).tar rm -rf $@ compress -c $(DISTDIR).tar > $@ $(DISTDIR).tar.gz: $(DISTDIR).tar rm -rf $@ gzip -c -9 $(DISTDIR).tar > $@ $(DISTDIR).tar.bz2: $(DISTDIR).tar rm -rf $@ bzip2 -k -9 $(DISTDIR).tar $(DISTDIR).tar.xz: $(DISTDIR).tar rm -rf $@ xz -k $(DISTDIR).tar $(DISTDIR).shar: $(DISTDIR) shar $(DISTDIR) > $@ $(DISTDIR).shar.gz: $(DISTDIR).shar gzip -9 $(DISTDIR).shar $(DISTDIR).zip: $(DISTDIR) rm -rf $@ zip -rq $@ $(DISTDIR) distcheck: dist # Currently, distcheck requires tar that automatically uncompresses # the archive. tar -x -f $(DISTDIR).tar.gz (cd $(DISTDIR) && sh configure && make check dist) $(RM_DISTDIR) DISTFILES = $(SOURCES) $(SOURCES:.c=.d) $(HEADERS) README README.ja COPYING INSTALL INSTALL.ja NEWS NEWS.ja THANKS configure Makefile.in install-sh makedeps.yash distfiles: makedeps $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(topdir)/$(DISTTARGETDIR) makedeps: _PHONY $(TARGET) $(topdir)/$(TARGET) $(topdir)/makedeps.yash $(SOURCES) # ctags conforms to POSIX, but etags and cscope do not. CTAGS = @CTAGS@ CTAGSARGS = @CTAGSARGS@ ETAGS = @ETAGS@ ETAGSARGS = @ETAGSARGS@ CSCOPE = @CSCOPE@ CSCOPEARGS = @CSCOPEARGS@ tags: $(SOURCES) $(HEADERS) $(CTAGS) $(CTAGSARGS) TAGS: $(SOURCES) $(HEADERS) $(ETAGS) $(ETAGSARGS) cscope: cscope.out cscope.out: $(SOURCES) $(HEADERS) $(CSCOPE) $(CSCOPEARGS) mostlyclean: _mostlyclean -@+(cd builtins && $(MAKE) mostlyclean) -@+(cd doc && $(MAKE) mostlyclean) -@+(cd lineedit && $(MAKE) mostlyclean) -@+(cd po && $(MAKE) mostlyclean) -@+(cd tests && $(MAKE) mostlyclean) _mostlyclean: -rm -rf $(OBJS) $(BYPRODUCTS) $(DISTDIR) clean: _clean -@+(cd builtins && $(MAKE) clean) -@+(cd doc && $(MAKE) clean) -@+(cd lineedit && $(MAKE) clean) -@+(cd po && $(MAKE) clean) -@+(cd tests && $(MAKE) clean) _clean: _mostlyclean -rm -rf $(TARGET) share/config $(DISTS) distclean: -@+(cd builtins && $(MAKE) distclean) -@+(cd doc && $(MAKE) distclean) -@+(cd lineedit && $(MAKE) distclean) -@+(cd po && $(MAKE) distclean) -@+(cd tests && $(MAKE) distclean) -@+$(MAKE) _distclean _distclean: _clean -rm -rf Makefile config.log config.status config.h tags TAGS cscope.out maintainer-clean: -@echo 'This command is intended for maintainers to use;' -@echo 'it deletes files that may need special tools to rebuild.' -@+(cd builtins && $(MAKE) maintainer-clean) -@+(cd doc && $(MAKE) maintainer-clean) -@+(cd lineedit && $(MAKE) maintainer-clean) -@+(cd po && $(MAKE) maintainer-clean) -@+(cd tests && $(MAKE) maintainer-clean) -@+$(MAKE) _distclean -rm -rf $(SOURCES:.c=.d) config.h: config.status $(SHELL) config.status $@ Makefile: Makefile.in config.status $(SHELL) config.status $@ config.status: configure $(SHELL) config.status --recheck .PHONY: all test tests check tester mofiles docs man html install install-strip install-binary install-binary-strip install-data install-html installdirs installdirs-binary installdirs-data installdirs-data-main installdirs-html uninstall uninstall-binary uninstall-data dist dist-tarZ dist-gzip dist-bzip2 dist-xz dist-shar dist-zip dist-all distcheck distfiles copy-distfiles makedeps cscope mostlyclean _mostlyclean clean _clean distclean _distclean maintainer-clean _PHONY: @MAKE_INCLUDE@ alias.d @MAKE_INCLUDE@ arith.d @MAKE_INCLUDE@ builtin.d @MAKE_INCLUDE@ exec.d @MAKE_INCLUDE@ expand.d @MAKE_INCLUDE@ hashtable.d @MAKE_INCLUDE@ history.d @MAKE_INCLUDE@ input.d @MAKE_INCLUDE@ job.d @MAKE_INCLUDE@ mail.d @MAKE_INCLUDE@ makesignum.d @MAKE_INCLUDE@ option.d @MAKE_INCLUDE@ parser.d @MAKE_INCLUDE@ path.d @MAKE_INCLUDE@ plist.d @MAKE_INCLUDE@ redir.d @MAKE_INCLUDE@ sig.d @MAKE_INCLUDE@ strbuf.d @MAKE_INCLUDE@ util.d @MAKE_INCLUDE@ variable.d @MAKE_INCLUDE@ xfnmatch.d @MAKE_INCLUDE@ xgetopt.d @MAKE_INCLUDE@ yash.d yash-2.49/NEWS000066400000000000000000001326321354143602500131250ustar00rootroot00000000000000History of Yash Legend: +: new feature -: removed feature =: specification change *: bug fix x: new bug ---------------------------------------------------------------------- Yash 2.49 + '--for-local' option. = The '--no-unset' option now rejects unset variables not only in parameter expansion but also in arithmetic expansion. * Expansion of ""$*, ""$@, $*"", and $@"" now correctly yields an empty string rather than nothing when there are no positional parameters. * The $RANDOM variable was expanding to a value larger than 32767 on some systems. * The "\e" escape sequence was not working in the "echo" built-in. * When a last command is a subshell, the parent shell's jobs were not being cleared when entering the subshell. * The job status was not being updated correctly if a process ID was reused by another process before the previous process was removed from the job list. * The "typeset" built-in now prints functions that contain a simple command whose command name is a keyword in a format that can successfully re-parsed by the shell. . Updated completion scripts: * Remote branch names are now correctly completed for the argument to Git remote/fetch/pull/push commands. * Local pathname operands are now correctly completed for the rsync command. ---------------------------------------------------------------------- Yash 2.48 + The double-bracket command (the [[ ... ]] syntax) + The "local" built-in + The '--le-predict-empty' option + The prompt string now can be defined with the $YASH_PS... variables. = Command line prediction no longer shows suggestion before you start typing a command. Use the new '--le-predict-empty' option to restore the previous behavior. = The default value of $PS1 has been changed. * The line number is now correctly counted in arithmetic expansions that contain newlines. * Subshells in the EXIT trap were unexpectedly exiting with the exit status of the last command executed before the EXIT trap on the main shell. * A new EXIT trap that was set in a subshell in the EXIT trap was unexpectedly being ignored. * The "typeset" built-in was crashing when printing a function that contains a here-document that contains a command substitution that contains more than one command. * The "typeset" built-in was forgetting to print here-document contents when printing a function that contains a process substitution (or redirection) that contains here-documents. * The variable name token in the for command and the word following a here-document redirection operator are now correctly parsed even when it resulted from an alias substitution whose value begins with a blank. * The "do" keyword in a for loop is no longer subject to alias substitution. * An invalid semicolon that appears at the beginning of a line as a result of alias substitution in a for loop is now correctly rejected. . For more strict POSIXly-correctness, some syntactic constructions are now regarded as an error in the POSIXly-correct mode: * An IO_NUMBER token cannot be the operand of a redirection. * Keywords immediately following a redirection are not recognized. . Updated the sample initialization script (yashrc): = The prompt strings are now defined with the $YASH_PS... variables. * Window title update should now work on more terminals. * Any predefined handlers for SIGTSTP, SIGTTIN, and SIGTTOU are now cancelled so that jobs can be suspended properly. . Updated completion scripts: * git: pathnames are now correctly completed with the latest Git. + git-grep: support new options in Git 2.19.1. + git-stash: support new options in Git 2.18.0. + ping: support some common options = ssh, ssh-keygen: support new options in OpenSSH 7.7. ---------------------------------------------------------------------- Yash 2.47 + '--errreturn' option. = Expansion results printed by the -x option is now quoted to disambiguate presence of special characters. = When the shell prints aliases, variables, key bindings, etc. they are now printed with less quotes. * The "set" built-in without any argument now prints not only local variables but also global. * The "." built-in no longer leaves temporary positional parameters after a file-not-found error. * The ">" redirection with the noclobber option is now more reliable than before. Previously, there was little possibility of overwriting an existing regular file in case another process simultaneously replaces the file. . Updated the sample initialization script (yashrc): + Example code for enabling "direnv". ---------------------------------------------------------------------- Yash 2.46 = Global aliases are now substituted in all locations, including part of compound commands where only operators are syntactically acceptable. * The shell now reads and executes all shell commands line by line. Previously, commands that are not from a file or the standard input were parsed all at once before being executed. * After alias substitution where the alias value ends with a blank, the next word is also subject to alias substitution, but previously this substitution was being applied only once, which was a different behavior from many other shells. * After alias substitution where the alias value ends with a blank, global aliases were being applied twice, which is now just once. * Line continuations no longer prevent recursive alias substitution. ---------------------------------------------------------------------- Yash 2.45 + [line-editing] new line-editing commands: complete-max-then-list, complete-max-then-next-candidate, complete-max-then-prev-candidate = In prompt strings $PS1 and $PS2, the job count printed by the \j notation included finished jobs that have not yet waited for. That was confusing, so the job count no longer includes finished jobs that have once had its "Done" status reported to the user. = The "jobs" built-in, with the -n option, no longer clears finished jobs that are not being reported. * In arithmetic expansion, unset variables now successfully expand to a value of "0". * With the "-o notify" option, job status change was not being printed until the shell receives a SIGCHLD. . Updated the default initialization script (yashrc): + Confirm before clearing the whole history by "history -c'. . Updated completion scripts: + carthage: --cache-builds option * git: at most 10 candidates are now proposed for a commit hash. ---------------------------------------------------------------------- Yash 2.44 + Command line prediction and the '--le-predict' option. + A default initialization script is automatically loaded if the ~/.yashrc file cannot be loaded. = During shell startup, $YASH_LOADPATH is now initialized only if not set in the environment. * Updated completion script for: carthage, git, git-log, git-revert, git-submodule, ssh * The "return" built-in was wrongly refusing to return from a script sourced by the "." built-in in an interactive shell. * In the "test" built-in, the unary "-o" operator was returning the wrong answer if the given option name was negated with the "no" prefix (e.g. "test -o noclobber"). * The "wait" built-in now returns an exit status of 1 if any operand is invalid, ignoring results for any other operands. * In a for loop without the "in" keyword, a semicolon that delimits the variable name, if any, now must appear on the same line as the variable name. = Support for POSIX.1-2008 Technical Corrigendum 2 (2016). This affects the shell's behavior only in the POSIXly-correct mode unless marked with (*) in the following: . In a pipeline, the reserved word "!" must now be delimited by a white space when followed by a parenthesis "(". . In a for loop without the "in" keyword, the variable name and the "do" keyword can now be separated by a semicolon. . Variables that are assigned in a simple command whose command is a function no longer persist after the function finishes. . The standard input of an asynchronous list is now implicitly redirected to /dev/null when job control is off. Previously, this was done when the shell was interactive in the POSIXly- correct mode. . The SIGINT and SIGQUIT signals on an asynchronous list are now ignored only when job control is off. . The "bindkey", "complete", "dirs", "disown", "hash", "help", "history", "popd", "pushd", "suspend", "type", "typeset", and "ulimit" built-ins are now semi-special built-ins. . The "break" and "continue" built-ins now interrupt lexically enclosing loops only. (*) . Unquoted $* and $@ now expand to separate positional parameters, even if $IFS is an empty string. (*) . On an assignment error, a non-interactive shell now always exits, regardless of command type. (*) . When any error occurs in a special built-in in non-interactive shell, the shell now exits, regardless of error type. ---------------------------------------------------------------------- Yash 2.43 * Updated completion script for: carthage, cd, su ---------------------------------------------------------------------- Yash 2.42 + '--pipefail' option. + New completion script for: carthage, dnf, git-rev-parse, tree, watch + Updated completion script for: cd, git, git-bisect, git-rev-list (Git 2.9.2). = Yash now supports the 2013 edition of POSIX.1-2008. = Unclosed here-documents are now always rejected. = A job-control shell now ignores SIGTTIN and SIGTTOU by default. = Line-editing can now be interrupted by SIGINT. = The "-o errexit" option is now applied to redirection errors on compound commands and expansion errors in the for and case commands. * In backquoted command substitutions that occur in double quotes, backslashes that escape a double quote are now handled before the containing command is parsed. ---------------------------------------------------------------------- Yash 2.41 + '--emptylastfield' option. + New option for the "shift" built-in: -A + The "shift" built-in now accepts negative counts. + New completion script for: git-notes, git-reflog, git-worktree = The non-interactive shell now exits on an assignment error in a simple command without a command name. = The exit status is now non-zero when a for loop has been interrupted because of an read-only variable. = When there is no positional parameter, the word """$@" is now expanded to an empty word, producing the same result as "$@""". = The behavior of field splitting has been modified to match the intended interpretation of POSIX: If field splitting yields more than one field and the last field is empty, the last field is now removed. = The "read" built-in now removes leading whitespaces when assigning the last input value to the variable. = The first character of $IFS is now always used as a separator when concatenating positional parameters. Previously, $IFS was ignored in some corner cases. * Unclosed process redirections are now detected as syntax error. * When $ECHO_STYLE was GNU or ZSH, the "echo" built-in was incorrectly ignoring single hyphen operands. * The "typeset" built-in was crashing when used with a temporary assignment to a variable not specified in the built-in operands. * In the "typeset" built-in, the -r option was not effective when printing functions. * The "typeset" built-in now detects combination of the -f and -g options as an error. * The "fg" and "bg" built-ins no longer try to resume a non-job- controlled job when given no operand. * In range brace expansion, integers were not always parsed correctly. * The "fc" built-in was not handling the history range correctly when the range was specified with an unused entry number. * The "fc" built-in was incorrectly rejecting the syntax of the form "fc -s foo=bar n". * The interactive shell was unexpectedly forgetting the exit status of background jobs when reporting the job status. * Fixed potential undefined behavior during reading a history file. * Parameter expansions of the form ${foo/bar/baz} are now rejected in the POSIXly-correct mode, as documented in the manual. * Single- or double-quoted empty words were incorrectly being removed in field splitting. * Backslash escapes are now recognized in parameter indexes. ---------------------------------------------------------------------- Yash 2.40 = The "unset" built-in no longer rejects variable names containing the "=" symbol as an error. They are now silently ignored. = In the POSIXly-correct mode, unclosed here-documents are now treated as an error. * Fixed possible arithmetic overflow errors in memory allocation which might result in an undefined behavior. * Fixed crash on assigning a floating-point number in arithmetic expansion. * Fixed memory leak on finding a command in a relative path from $PATH. * The parameter expansion ${foo##bar*} was being treated like ${foo##bar} where the asterisk should match up to the end of the parameter value. * The parameter expansion ${foo%%*} was being expanded to ${foo} where it should expand to an empty string. * The "getopts" built-in no longer rejects digits as option characters. * The non-interactive shell no longer exits when the "command" built-in executes the "." built-in and the script is not found. * Fixed incorrect exit status of simple commands where command substitution is performed during command word expansion which yields no command line words. * The "exec" built-in now correctly exits with an exit status of 127 for a script file not found. * Pipes were not being connected as expected during command word expansion. * The "fg" and "bg" built-ins now report an error when they fail to print the job name. ---------------------------------------------------------------------- Yash 2.39 + New options for the "read" built-in: -e, -P, -p + New completion script for: passwd, valgrind - The configuration option '--disable-alias' has been dropped. = The "read" built-in now requires the -e (--line-editing) option to enable line-editing. = The behavior of the "trap" built-in now conforms to POSIX.1-2013. The built-in prints the traps for the calling shell rather than the subshell when it is executed in a command substitution. = The manual for the "return" built-in has been clarified as to what the built-in can return from. The behavior of the built-in has been modified accordingly. It no longer interrupts the "eval" and "fc" built-ins. * Crash on division by zero with the "/=" or "%=" operator in arithmetic expansion. * Crash in the "complete" built-in called without the -D option. * Yash no longer tries to find the profile and rcfile scripts in the root directory when $HOME is not set. * The "read" built-in with a single operand was failing to trim initial IFS whitespaces in field splitting. * The "read" built-in was not handling backslashes correctly in field splitting if $IFS contained a backslash. * The "-o allexport" option was being ignored when an array is assigned by the "read" built-in with the -A option. * Traps are no longer handled while waiting for input with line- editing in the "read" built-in. * Operands to the "set" built-in are now correctly completed. * Minor fix in completion script for "git-svn". ---------------------------------------------------------------------- Yash 2.38 + New completion scripts for: git-rm = In the POSIXly-correct mode, functions must now be defined with a portable name. * Backslashes following IFS characters were wrongly being dropped in field splitting. * In the POSIXly-correct mode, non-portable variable names are no longer allowed in for loops. * Completion error on words following a tilde expansion. * Completion error on moved or copied tracked files for git-add and git-commit. * Completion error on untracked file for git-commit. ---------------------------------------------------------------------- Yash 2.37 + Updated completion scripts for: git-checkout (Git 2.1.2), git-push (Git 2.0.1), git-submodule (Git 2.1.2). ---------------------------------------------------------------------- Yash 2.36 + New completion scripts for: git-describe, tig. + Updated completion script for: git-branch (Git 1.9.0). ---------------------------------------------------------------------- Yash 2.35 + '--traceall' option. + New completion scripts for: git-clean, git-grep, git-ls-remote, git-submodule, git-whatchanged + Updated completion scripts for: git-cherry-pick, git-rebase (Git 1.8.1.4), ssh, ssh-add, ssh-keygen (OpenSSH 6.2) = The condition for exiting the shell when the -e option is enabled has been changed in accordance with the 2013 edition of POSIX.1-2008. = The "++" and "--" operators are no longer supported in the POSIXly-correct mode. = The $RANDOM variable now always returns random numbers, ignoring the value from the environment variable, if any. = If yash encounters a too long line that cannot be handled while reading a history file, it now tries to keep reading the rest of the file rather than stopping reading. = Very long command lines are no longer saved in the history file. * Minor fix in a syntax error message. * Minor fix in completion script for "git", "ssh" and "rsync". ---------------------------------------------------------------------- Yash 2.34 + New "test" built-in operator: =~, -o (unary) + New completion scripts for: configure, git-name-rev, git-request-pull, make, rsync = A syntax error or expansion error in .yash_profile or .yashrc no longer makes the shell exit even when the shell is not interactive. = In line-editing, overwritten characters are now restored when you hit the backspace key. = In line-editing, the undo history is no longer saved for each backspace. * Fixes in completion scripts for "git". ---------------------------------------------------------------------- Yash 2.33 = The "help" built-in now prints brief usage of built-ins. = Some error messages for command syntax error have been revised. = Some built-ins now check command syntax more strictly. * The "set" built-in aborts the shell on a command syntax error as specified in POSIX. ---------------------------------------------------------------------- Yash 2.32 + Man page and HTML manual in English and Japanese. = The "help" built-in now prints part of the man page. * Fixes in completion scripts for "git". ---------------------------------------------------------------------- Yash 2.31 + Completion of directory stack indices in extended tilde expansion. + New option for the "complete" built-in: --dirstack-index + Completion for "svn" version 1.7. * Fixes in "git", "tar" and "su" completion scripts. ---------------------------------------------------------------------- Yash 2.30 * Fixed pathname completion for redirections. + New completion scripts for: svn, git, gitg, gitk, and gitx. * Minor fixes in completion scripts. ---------------------------------------------------------------------- Yash 2.29 + An alias value now can contain newlines. * Fixed invalid memory access on printing an empty array using the "typeset" built-in. * The function body is now correctly executed in a subshell when the body is defined using the (...) compound command. * In line-editing, the undo command can now be applied only to the history entry that is being edited. ---------------------------------------------------------------------- Yash 2.28 = In an interactive shell, mail check is now done before job status report. * Fixed arithmetic expansion evaluating some expressions that should not be evaluated. * Fixed the "array" built-in removing the wrong elements when positive and negative indices are intermixed with the -d option. * Fixed the EXIT trap being wrongly executed after a command-not- found error. * The EXIT trap is now executed with the correct redirection when the last command had redirection. * The "exec" built-in now correctly exports arrays that are just assigned. * ${#@} is now correctly expanded to an array of integers that represent the lengths of array elements. * Fixed infinite loop in sequential brace expansion. * Fixed configuration file parsing in completion of the "ssh" command. * The "-T" option of the "ln" and "mv" commands now can be completed. * The EXIT trap now can be completed. ---------------------------------------------------------------------- Yash 2.27 * Minor bug fixes. + New completion scripts for: awk, chsh, gawk, nawk, pgawk, scp, sftp, ssh-add, ssh-agent, ssh-keygen, su, sudo, sudoedit, and useradd. * Fix in completion scripts for the "set" and "tar" commands. ---------------------------------------------------------------------- Yash 2.26 + New option for the "return" built-in: -n (--no-return) = Error messages have been revised. = Shell option names have been generalized: Option names are now case-insensitive. Non-alphanumeric characters are ignored in option names. Options can be inverted by prefixing "no" to their names. = "case foo in (esac) bar; esac" is now rejected in the POSIXly correct mode. = The "-o noexec" option is now ignored in an interactive shell. = New mail notification is now printed only when the mailbox file is not empty. = "printf %c ''" now prints nothing as "printf %c" do. = In an invocation of the "complete" built-in with the -A or -R option, the pattern is now matched against the last component of the pathname when generating pathname candidates with the -f option. * Function names containing an '=' sign can now be specified as arguments to the typeset and unset built-ins (with the -f option). * Fixed the exit status of iterative execution that was interrupted by SIGINT. + New completion scripts for: bsdtar, eview, evim, gex, gnutar, gtar, return, rgview, rgvim, rview, rvim, slogin, ssh, tar, and which. * Many fixes in completion scripts for: bash, chmod, dash, less, mksh, set, sh, and umask. ---------------------------------------------------------------------- Yash 2.25 = In the completion candidate list, options are now sorted case- insensitively. = While executing a completion function, the $IFS is reset to the default value. + New completion scripts for: bash, csplit, dash, diff, ed, egrep, env, ex, expand, fgrep, file, find, fold, getconf, grep, gview, gvim, gvimdiff, head, iconv, id, join, ksh, less, ln, locale, man, mesg, mkdir, mkfifo, mksh, more, mv, newgrp, nice, nl, nohup, od, paste, patch, pathchk, pr, ps, renice, rm, rmdir, sed, sh, sort, split, stty, tail, tee, time, touch, tr, uname, uniq, vi, view, vim, vimdiff, wc, who, xargs, and yash. * Many fixes in completion scripts for: cat, cd, chgrp, chmod, chown, cmp, comm, cp, crontab, cut, date, df, du, exec, ls, and set. ---------------------------------------------------------------------- Yash 2.24 + New option for the "." built-in: -L + New options for the "command" built-in: -a, -f, -k - Removed the -B option for the "command" built-in. = Fixed configuration process so that the shell can handle all kinds of signals on FreeBSD. = [line-editing] New command completion mechanism. The "complete" built-in now has new syntax and semantics. Completion settings are now autoloaded from $YASH_LOADPATH. + New completion scripts for: basename, bg, cat, chgrp, chmod, chown, cmp, comm, command, cp, crontab, cut, date, df, du, export, popd, pushd, readonly, type, ".", and "[". = Revised completion scripts for: alias, array, bindkey, break, cd, complete, continue, dirs, disown, echo, eval, exec, exit, fc, fg, getopts, hash, help, history, jobs, kill, ls, printf, pwd, read, set, suspend, test, trap, typeset, ulimit, umask, unalias, unset, and wait. - Removed completion script for the "return" built-in. = Short options now come before long options in a completion candidate list. * Symbolic links to non-existent files can now be completed. * Other fixes in command completion. * Fixed invalid memory access on parameter expansion failure in redirection. ---------------------------------------------------------------------- Yash 2.23 * Traps are no longer handled during completion to avoid messing up the display. * Fixed invalid memory access during completion. When the notify option is enabled, the shell was wrongly trying to notify the job status while executing a candidate generator function. * The "-o verbose" option is now disabled during completion to suppress the auto-loaded script being printed. * Fixed completion script for the "kill" built-in. ---------------------------------------------------------------------- Yash 2.22 + New "test" built-in operators: -G, -O, -N + Completion settings are now autoloaded from $YASH_COMPPATH. + New completion scripts for: alias, array, bindkey, break, cd, complete, continue, dirs, disown, echo, eval, exec, exit, fc, fg, getopts, hash, help, history, jobs, kill, ls, printf, pwd, read, return, set, suspend, test, trap, typeset, ulimit, umask, unalias, unset, and wait. * Fix a bug that was causing a signal received by a just-created subshell to be wrongly ignored. * Other bug fixes ---------------------------------------------------------------------- Yash 2.21 + Command line completion = In an interactive shell, the value of $LINENO is now reset to 1 every time a command is input and executed. * The "function" keyword was not recognized by "command -v". * Fixed handling of SIGINT so that the interactive shell properly gets back to normal after receiving SIGINT. * Input reading functions rewritten, including some bug fixes. ---------------------------------------------------------------------- Yash 2.20 + Function definition using the "function" keyword. = The "-nt" and "-ot" operators of the "test" built-in now consider a non-existent file older in favor of Korn shell. = [line-editing] some editing commands have been renamed: vi-change-all -> vi-change-line vi-yank-and-change-all -> vi-yank-all-change-line vi-append-end -> vi-append-to-eol * [line-editing] the clear-and-redraw-all command was broken. ---------------------------------------------------------------------- Yash 2.19 + The "ulimit" built-in is now available on FreeBSD. + In mail checking, the timestamp of the mail file is now compared by the nanosecond if possible. + [line-editing] new line-editing commands: oldest-history-bol, newest-history-bol, return-history-bol, prev-history-bol, next-history-bol, beginning-search-forward, beginning-search-backward = Faster filename pattern matching. = [line-editing] the "accept-line" command no longer accepts a failing history search result. = [line-editing] the following commands have been changed not to move the cursor to the beginning of the line: oldest-history, newest-history, return-history, prev-history, next-history The new commands whose names end with "-bol" provide the previous behaviors. * Fixed broken output of array assignment traces. * [line-editing] the "vi-edit-and-accept" command now handles the count. * [line-editing] fixed the cursor position after the search-again-forward/backward command in the emacs-like mode. ---------------------------------------------------------------------- Yash 2.18 + New operators for string comparison in the "test" built-in: ==, ===, !==, <, <=, >, >= + Operators "-nt" and "-ot" in the "test" built-in now compares the modification time by the nanosecond if possible. + '--default-directory' option for the "cd" and "pushd" built-in. + '--remove-duplicates' option for the "pushd" built-in. + The right prompt and the styler prompt. + '--le-alwaysrp' option. = The interpretation of escape sequences to change the font in $PS1 and $PS2 has been changed. = $PS3 no longer initialized. = Escape sequences are now interpreted in $PS4 as well as in $PS1 and $PS2. = The "echo" and "printf" built-ins now can print a null character. * [line-editing] $LINES and $COLUMNS were wrongly ignored. * [line-editing] fixed font color for some terminals. ---------------------------------------------------------------------- Yash 2.17 + '--le-visiblebell' option. + New operators for version comparison in the "test" built-in: -veq, -vne, -vgt, -vge, -vlt, -vle + [line-editing] "eof" command = The "typeset" built-in now prints only local variables when invoked without the -g option or operands. = The "typeset" built-in now prints function definitions in a pretty format. = When not in POSIXly-correct mode, commands in command substitu- tion are now parsed when the command containing the substitution is parsed. * Fixed the parse error on parameter expansion "${#}". * Fixed the "wait" built-in wrongly trying to wait for non-parent processes when invoked in a subshell. * [line-editing] vi-replace-char command was broken. * Many other bug fixes ---------------------------------------------------------------------- Yash 2.16 + '--curbg' and '--curstop' options. = '--curasync' option is now set by default. = Changed exit status of a while/until loop whose body is empty. = The "return" and "exit" built-ins now accepts an exit status of 256 or larger. = The "echo" built-in now interprets octal escapes only beginning with a backslash followed by a zero. * Fixed a syntax error of a special built-in invoked thru the "command" built-in causing the non-interactive shell to exit. * Fixed the exit status of the shell when the EXIT trap is set. * The '--allexport' option was wrongly ignored by the "read" and "getopts" built-ins. * Fixed segfaults and other errors in the "getopts" built-in. * [line-editing] Fixed the vi-replace-char command causing invalid memory access. * [line-editing] Fixed the emacs-just-one-space command not leaving space(s). ---------------------------------------------------------------------- Yash 2.15 + An interactive shell now can be interrupted by SIGINT. + Bracket expressions in pathname expansion are now fully supported (provided that libc's regex implementation is correct). = The "suspend" built-in now requires the -f option to suspend an interactive shell that is a session leader. = The shell no longer automatically sets the $YASH_LE_TIMEOUT variable. = A job-control shell now stops itself when invoked in the background. = A job-control shell no longer ignores SIGTTOU by default. = The '--nocaseglob' option no longer affects pattern matching in parameter expansions of the form "${var#xxx}", "${var%xxx}", etc. * Fixed pathname expansion on redirection target. * Couldn't set a signal handler to the default by the "trap" built-in if the signal was ignored on invocation of an inter- active shell. * Fixed accidental interruption of built-ins by signals. * Fixed parser so that a non-simple command is properly parsed after alias substitution. * Various bug fixes ---------------------------------------------------------------------- Yash 2.14 + The -l option for the "bindkey" built-in. + Negative array indices are now allowed. = $PWD is now set to "/" (rather than "/..") after "cd /..". = The "command" built-in with the -vb option now ignores shell keywords, aliases and functions. = Most of the built-ins now prints an error message and returns non-zero when they cannot print something to the standard output. = The "pwd" and "times" built-ins now fail when an operand is given. = The shell no longer exits when an assignment for a special built-in failed if not in POSIXly-correct mode. * "cd //" was failing if the current directory is "/". * Invoking the "bindkey" built-in in the yashrc file was causing an invalid memory access. * The "command" built-in with the -vb option was failing to find regular built-ins that are not in $PATH. * The "dirs" command was going into an infinite loop if given an argument. * The -c and -d options are wrongly rejected by the "ulimit" built-in. * The "fg" built-in now refuses more than one operands in POSIXly- correct mode, as described in the help. ---------------------------------------------------------------------- Yash 2.13 + "history" built-in + The -i option for the "eval", "break" and "continue" built-ins. + The value of the $COMMAND_NOT_FOUND_HANDLER variable is now executed when a command is not found. - '--autocd' option = The exit status is now one when the "return" built-in is used outside a function in an interactive shell. = Revised command search and execution. * The shell would not exit after the "exit" built-in twice in a row when you have $PROMPT_COMMAND set and you have stopped jobs. * The "notifyle" option not working with $PROMPT_COMMAND set. * [line-editing] Redoing was broken. * [line-editing] The command line was not redrawn after trap handling. * [line-editing] Fixed some bugs in the vi-edit-and-accept command. ---------------------------------------------------------------------- Yash 2.12 + Emacs-like line-editing. + "bindkey" built-in. + The $PROMPT_COMMAND variable now can be an array. + The value of the $YASH_AFTER_CD variable is now executed after the working directory was changed. = The "fg" and "bg" built-in now always sends SIGCONT to the continued job. = The "exit" built-in now warns about stopped jobs even when executed after the "fg", "bg", "disown" or "wait" built-in. * In vi-like line-editing, the wrong text was put after 30th yank. * In vi-like line-editing, the "s" command cannot be used if the cursor is at the beginning of line ---------------------------------------------------------------------- Yash 2.11 + Added the "--histspace" and "--le-noconvmeta" options. + Support for the $HISTRMDUP variable. + Support for the $YASH_LE_TIMEOUT variable. + The "kill" built-in with the "-l" option now accepts signal names as operands. = The "--le-convmeta" option now is a Boolean option. = The "$-" special parameter now includes the "l" flag if the shell is a login shell. * An empty case command "case i in (*) esac" now always returns the exit status of zero. * Quoted words were incorrectly expanded with backslashes when the "-f" option is set. * Fixed invalid memory access in the "v" command of vi-like line-editing. ---------------------------------------------------------------------- Yash 2.10 + History search in line-editing. = Empty lines are no longer stored in the history. = In the vi-like line-editing, "cw" and "cW" now work as in vi. * Setting the "notifyle" option caused a segfault if yash was configured with line-editing disabled. * In line-editing, undoing a change to a history entry did not restore the cursor position properly. * Some fixes for invalid memory access during line-editing. ---------------------------------------------------------------------- Yash 2.9 + Line-editing in the interactive mode. x Line-editing is not fully implemented. + Multiple instances of shell that use the same file for the history file now share the history. - The "history" built-in has been removed. = Now non-ASCII alphabets are allowed in variable names. = Now nested parameter expansions must be enclosed by braces. = The "help" built-in now prints an error message if the specified built-in is not found. * Fixed floating-point arithmetics in arithmetic expansions. * Fixed parser for arithmetic expansions that was incorrectly rejecting identifiers that start with underscores. * Fixed parser that had trouble parsing parameter expansions containing the hash sign like "${#=x}". * Fixed the help message for the "pwd" built-in. ---------------------------------------------------------------------- Yash 2.8 + Brace expansion with delta: {a..b..c} + The "command" built-in's -b and -B options now can be used with the -v and -V options. = Yash now conforms to POSIX.1-2008. . The "read" built-in now always removes trailing white-spaces from the input. . The results of tilde expansion are no longer subject to field splitting and pathname expansion. . The "pwd" built-in with the -P option no longer sets the $PWD variable. . "cd -L foo/.." is no more the same as "cd -L ." in that it is an error when the directory "foo" does not exist. . The "command" built-in's -p option now can be used with the -v and -V options. . In POSIXly-correct mode, all asynchronous commands now ignore SIGINT and SIGQUIT (even when job control is active). * When executing commands edited by the "fc" built-in, the $? variable was incorrectly assigned the exit status of the editor invoked by "fc". * Backslashes, commas and braces in $IFS were incorrectly ignored in field splitting. * Pathname expansion failed if we do not have the read permission for the specified directory even when we only need the search permission. * The signal mask of commands invoked by the shell now inherits that of the shell (except for trapped signals). * The "command" built-in now properly handles directories given as the commands when the "autocd" option is on. ---------------------------------------------------------------------- Yash 2.7 + Parameter expansion ${array[index]:=value} now allows assignment to an empty array element. + Here-string by the "<<<" operator. + New redirection operator ">>|" opens a pipe. - Loop pipes no longer supported. = The $IFS variable is always initialized to the default value when the shell is invoked. * The "echo" and "printf" built-ins now print an error message on failure. * A quoted period at the beginning of a filename was not properly matching during filename expansion. ---------------------------------------------------------------------- Yash 2.6 + Added the -q option to the fc built-in. + Compound commands may now contain no commands inside. * "alias -p" now prints commands with proper escape. * In POSIXly-correct mode, a semicolon followed by the identifier followed by "for" is now treated as an error. * Global aliases are now allowed after compound commands. * The "fg" and "wait" built-ins were causing invalid memory access when the "-o notify" option is enabled. ---------------------------------------------------------------------- Yash 2.5 = Redirection of FDs used by the shell is now error. * Some redirection syntax errors were overlooked. * Fixed the "sig.y" test failure. * When an "exec" command with redirections is enclosed in a brace with redirections, the redirections to the brace are now properly closed after execution. * Fixed parsing error of a comment after the identifier of a for statement. ---------------------------------------------------------------------- Yash 2.4 = The long option for the -p option of the "jobs" built-in has been changed from "--pid-only" to "--pgid-only". * Trap of SIGCHLD, SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTOU were wrongly set to "ignore" in some condition. * Signal handlers for SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTOU were mistakenly reset in some moments. * Fixed the exit status of the "wait" built-in returned when interrupted by a signal. * Fixed file access permission test * "command -V xxx/yyy" now prints an error message if "xxx/yyy" is not a valid command. ---------------------------------------------------------------------- Yash 2.3 = Now changing LC_CTYPE immediately takes effect if the shell is interactive and not in the POSIXly-correct mode. * Fixed parameter expansion: empty words are now expanded properly. * Fixed a race condition, which was causing some signals ignored. * Assignments using the typeset/readonly/export built-ins failed to update the shell's internal data. This caused the shell to keep using the old PATH after the PATH has been changed. ---------------------------------------------------------------------- Yash 2.2 + "help", "pushd", "popd" and "dirs" built-ins = The value of $PWD set by the "cd" built-in is fixed. It now has a correct value when changing to a relative path from the root directory. * Pathname expansion not properly performed for patterns including "." or "..". ---------------------------------------------------------------------- Yash 2.1 + Array variables + "array", "echo", "printf" and "test" built-ins + The -A option for the "read" built-in * The colon flag in a parameter expansion like "${FOO:-bar}" was ignored if the value of the parameter begins with a certain character. * The readonly attribute was ignored when an assignment occurs against a command invocation * Passing "=" as a name to the "unset" built-in incorrectly unset the positional parameters and caused a potential invalid memory access. * Field splitting on variable values that consist only of spaces produced wrong results. * Test of file access permission now uses the effective user/group IDs rather than the real user/group IDs. ---------------------------------------------------------------------- Yash 2.0 + "history" built-in + Command redirection * A POSIXly-correct non-interactive shell exits when a particular error occurred on a special built-in according to POSIX. * Alias substitution routines were improved. * Other bug fixes ---------------------------------------------------------------------- Yash 2.0 beta2 + Command history + "type", "hash" and "fc" built-ins = If the "command" built-in with the -V option fails to find a command, an message is printed. * Invocation of an external command with an assignment to the $PATH variable caused an invalid memory access. * Single quote not parsed properly in some circumstances. * Other bug fixes ---------------------------------------------------------------------- Yash 2.0 beta1 + "read", "getopts" and "command" built-ins + '--autocd' and '--curasync' option + Mail check feature + The prompt command = The "readonly" and "export" built-ins now affects global variables by default. * The standard input is no longer buffered * The "typeset" built-in now allows any characters other than '=' for variable names. * The special parameter $? reflects the exit status of a command substitution in a variable assignment. * Many other bug fixes ---------------------------------------------------------------------- Yash 2.0 beta0 + "eval", "exec", ".", "times", "umask", "typeset", "export", "readonly", "unset", "shift" and "trap" built-ins + "wait" built-in now can be interrupted + Arithmetic expansions + Interactive shell now notifies the process killed by a signal - "/etc/profile" and "~/.profile" are no longer sourced on start-up * Fixed token delimitation in alias substitution * Fixed token delimitation on assignments without values * Many other bug fixes ---------------------------------------------------------------------- Yash 2.0 alpha2 + Initialization files such as "/etc/profile" and "~/.yashrc" are now sourced on start-up. + '--noprofile', '--norcfile' and '--rcfile' options + "pwd", "set", "exit", "return", "break", "continue", "jobs", "fg", "bg", "wait", "disown", "alias" and "unalias" built-ins * '--nocaseglob' was misinterpreted as '-c'. * It was not fully case-insensitive when '--nocaseglob' is on. * If job control is off, an interactive shell no longer prints changed status of jobs before prompt. = Error handling for unset parameters was improved. = The help message for 'yash --help' is now much briefer. ---------------------------------------------------------------------- Yash 2.0 alpha1 + New options implemented: -x, -h, -a = The default value of 'PS3' is not set in POSIXly-correct mode. + 'configure' accepts new '--no-undefined' option + ":", "true", "false", "cd" built-ins * Command hashtable is now cleared when PATH is assigned. * Assertion failure when a null character is input * Many other bug fixes ---------------------------------------------------------------------- Yash 2.0 alpha0 (the first release of version 2.x) x Arithmetic expansion is not implemented x Built-ins are not implemented at all yash-2.49/NEWS.ja000066400000000000000000001457071354143602500135250ustar00rootroot00000000000000Yash 更新履歴 凡例: +: 新機能 -: 廃止機能 =: 仕様変更 *: バグ修正 x: 新たに加わってしまったバグ ---------------------------------------------------------------------- Yash 2.49 + --for-local オプション = '--no-unset' オプションが有効な時、パラメータ展開だけでなく 数式展開でも未定義の変数をエラーにするようにした * 位置パラメータが無いとき ""$*, ""$@, $*"", $@"" が空文字列に 展開されず "$@" の様に扱われていた * 環境によっては $RANDOM 変数が 32768 以上の値に展開されることが あった * "echo" 組込みコマンドで "\e" エスケープシーケンスが 動いていなかった * コマンドリストの最後がサブシェルの場合、サブシェルの中で元の シェルのジョブをサブシェルのジョブとして扱ってしまっていた * ジョブが削除されるよりも前にプロセス ID が別のプロセスによって 使い回された場合、新しいジョブの状態が正しく更新されないことが あった * "typeset" 組込みコマンドで、コマンド名が予約語になっている 単純コマンドを含む関数が再解析可能な書式で出力されていなかった . 補完スクリプトを更新: * Git remote/fetch/pull/push コマンドの引数の補完でリモートの ブランチ名が正しく補完されていなかった。 * rsync コマンドの引数の補完でローカルのパス名が正しく補完されて いなかった。 ---------------------------------------------------------------------- Yash 2.48 + 二重ブラケットコマンド ([[ ... ]] 構文) + "local" 組込みコマンド + --le-predict-empty オプション + プロンプトを $YASH_PS... 変数で定義できるようにした = コマンドライン推定機能はコマンドを打ち始める前には動作しないのを デフォルトにした = $PS1 の初期値を変更 * 改行を含む数式展開の中で行番号が正しく数えられていなかった * EXIT トラップ内のサブシェルの終了ステータスが誤って元のシェルの EXIT トラップ開始前の終了ステータスになっていた * EXIT トラップ内のサブシェルで新たな EXIT トラップを設定しても 無視されていた * "typeset" 組込みコマンドで、複数のコマンドを含むコマンド置換を 含むヒアドキュメントを含む関数を表示するときクラッシュしていた * "typeset" 組込みコマンドで、ヒアドキュメントを含むプロセス置換 (またはプロセスリダイレクト) を含む関数を表示するとき ヒアドキュメントの内容が表示されていなかった * for ループの変数名トークンあるいはヒアドキュメント演算子の直後の トークンが、値が空白で始まるエイリアス置換の結果である場合に、 誤って構文エラーになっていた * for ループの "do" がエイリアス置換の対象になっていた * for ループでエイリアス置換により行頭に ";" が来る場合にエラーに ならないことがあった . POSIX 準拠モードで、POSIX で認められていない構文をエラーにした * IO_NUMBER トークンをリダイレクトの対象にできないようにした * 予約語をリダイレクトの直後に置けないようにした . 初期化スクリプト (yashrc) のサンプルを更新: = プロンプトを $YASH_PS... 変数で定義するようにした * より多くの端末で動作するようウィンドウタイトルの設定方法を変更 * SIGTSTP, SIGTTIN, SIGTTOU でジョブが確実に停止するように シグナルの無視を明示的にキャンセルするようにした . 補完スクリプトを更新: * git: 最新の Git でもファイルパスが正しく補完できるようにした + git-grep: Git 2.19.1 までの新しいオプションに対応 + git-stash: Git 2.18.0 までの新しいオプションに対応 + ping: いくつかの環境で共通のオプションをサポート = ssh, ssh-keygen: OpenSSH 7.7 までの新しいオプションに対応 ---------------------------------------------------------------------- Yash 2.47 + --errreturn オプション = -x オプションで出力する展開結果に含まれる空白等をクォートする ようにした。 = エイリアス・変数・キーバインドなどを出力する際の不要な引用符を 減らした。 * "set" 組込みを無引数で実行したときローカル変数しか出力されて いなかった。 * "." 組込みでファイルを開けなかったとき位置パラメータが元に戻って いなかった * -C オプションが有効な時の ">" リダイレクトの信頼性を改善した。 以前のバージョンではリダイレクトを開く瞬間に他のプロセスがほぼ 同時にファイルを置き換えた場合に -C オプションが無視されることが あった。 . 初期化スクリプト (yashrc) のサンプルを更新: + "direnv" を有効にするサンプルコード ---------------------------------------------------------------------- Yash 2.46 = グローバルエイリアスを複合コマンドの中を含む全ての箇所で置換 するようにした。 * コマンドを常に行単位で実行するようにした。以前は、ファイルや標準 入力からの読み込みでない場合、コマンドを全て構文解析した後に実行 していた。 * 空白で終わる値に置換されるエイリアスの直後では次の単語も エイリアス置換の対象となるが、このとき他のシェルと異なり一回しか 置換が働いていなかった。 * 空白で終わる値に置換されるエイリアスの直後でグローバルエイリアス が二回置換されていた。 * 行連結があるとエイリアス置換が正しく繰り返されないことがあった。 ---------------------------------------------------------------------- Yash 2.45 + 行編集: 新しいコマンドの追加: complete-max-then-list, complete-max-then-next-candidate, complete-max-then-prev-candidate = $PS1 および $PS2 のプロンプト内の \j 記法で表示されるジョブの 個数は、既に終了しかつ終了したことが報告済であるジョブを含まない ようになった = "jobs" 組込みを -n オプションで実行したとき、その場で出力されない 終了済みジョブを消去しないようになった * 数式展開で、未定義の変数は正しく 0 に展開されるようになった * "-o notify" オプションが有効でもシェルが SIGCHLD を受信するまで ジョブの状態が表示されていなかった . デフォルトの初期化スクリプト (yashrc) を更新: + history -c で履歴を完全削除する前に確認するようになった . 補完スクリプトを更新: + carthage: --cache-builds オプション * git: コミットハッシュの候補の数を 10 個までにした ---------------------------------------------------------------------- Yash 2.44 + コマンドライン推定と '--le-predict' オプション + ~/.yashrc ファイルが見付からないときはデフォルトの初期化ファイル を読み込むようになった = シェルの起動時、$YASH_LOADPATH は環境変数に存在しない場合のみ 初期化するようになった * 補完スクリプトを修正: carthage, git, git-log, git-revert, git-submodule, ssh * 対話シェルで "." 組込みで読み込んだスクリプトから "return" で戻る ことができなかった * "test" 組込みで単項 -o 演算子に "no" を付けたオプション名を指定 すると結果が逆になっていた ("test -o noclobber" など) * "wait" 組込みでオペランドが一つでも不正な時は必ず終了ステータス 1 を返すようになった * "in" を用いない for ループで、変数名の直後に改行を置いてその後に セミコロンで区切ってもエラーになっていなかった = POSIX.1-2008 Technical Corrigendum 2 (2016) のサポート。 (*) 印のものを除き、POSIX 準拠モードでの動作にのみ影響します。 . パイプラインで "!" の直後に "(" が来る場合は間に空白を入れる ことが必須になった . "in" を用いない for ループで、変数名と "do" の間をセミコロンで 区切れるようになった . 一つの単純コマンドで変数に代入しながら関数を実行した時、変数は 関数実行終了後には残らないようになった (*) . ジョブ制御が無効なときに非同期リストの標準入力を暗黙的に /dev/null にリダイレクトするようになった。以前は、POSIX 準拠 モードにおいてはシェルが対話シェルであるときにリダイレクトして いた。 . 非同期リストで SIGINT と SIGQUIT を自動的に無視するのはジョブ 制御が無効な時だけになった . "bindkey", "complete", "dirs", "disown", "hash", "help", "history", "popd", "pushd", "suspend", "type", "typeset", "ulimit" は準特殊組込みコマンドになった . "break" および "continue" 組込みコマンドは構文的に取り囲んで いるループに対してのみ使用できるようになった (*) . クォートされていない $* および $@ は $IFS が空文字列であっても 単語分割された状態に展開するようになった (*) . 非対話シェルで代入エラーが起きたとき、コマンドの種類によらず シェルが終了するようになった (*) . 非対話シェルの特殊組込みコマンドでエラーが起きたとき、エラーの 種類によらずシェルが終了するようになった ---------------------------------------------------------------------- Yash 2.43 * 補完スクリプトを更新: carthage, cd, su ---------------------------------------------------------------------- Yash 2.42 + '--pipefail' オプション + 補完スクリプトを追加: carthage, dnf, git-rev-parse, tree, watch + 補完スクリプトを更新: cd, git, git-bisect, git-rev-list (Git 2.9.2). = POSIX.1-2008 の 2013 年版に準拠 = 閉じられていないヒアドキュメントを常にエラーにするようになった = ジョブ制御有効なシェルはデフォルトで SIGTTIN と SIGTTOU を 無視するように = 行編集を SIGINT で中断・リセットできるようになった = "-o errexit" オプション有効時は、複合コマンドのリダイレクトエラー および for ならびに case コマンドでの展開エラーでもシェルを終了 するようになった * 二重引用符内の `...` によるコマンド置換の中では、バックスラッシュ による二重引用符のエスケープはコマンド置換のコマンドが解釈される よりも前に解釈されるようにした ---------------------------------------------------------------------- Yash 2.41 + '--emptylastfield' オプション + "shift" 組込みの新しいオプション: -A + "shift" 組込みの引数に負数を指定できるようになった + 補完スクリプトを追加: git-notes, git-reflog, git-worktree = コマンド名の無い単純コマンドで変数代入がエラーになった時、対話 シェルでなければすぐにシェルが終了するようになった = For ループが読み取り専用の変数に代入しようとしたときは 0 でない 終了ステータスを返すようにした = 位置パラメータが無いとき """$@" を "$@""" と同様に空の単語に展開 するようになった = 単語分割での空単語の扱いを POSIX の意図に合わせて変更 (分割結果が 二単語以上で最後の単語が空単語の時、空単語を削除するようになった) = "read" 組込みで最後の値を変数に代入するとき先頭に余分な空白を 残さないようになった = 位置パラメータを連結するとき常に $IFS の最初の文字で区切るように なった (以前は $IFS が無視される場合があった) * プロセスリダイレクトの閉じ忘れをエラーとして検出できていなかった * "echo" 組込みで、$ECHO_STYLE 変数が GNU または ZSH のとき、 単一のハイフンがオペランドとして出力されていなかった * "typeset" 組込みで、引数で指定していない変数への一時代入がある 場合にクラッシュしていた * "typeset" 組込みで関数を出力する時 -r オプションが無視されていた * "typeset" 組込みで -f と -g オプションの同時使用をエラーと するようにした * "fg" および "bg" 組込みにオペランドを指定しなかった場合に ジョブ制御対象外のジョブを再開しないようにした * 連続した数値のブレース展開で整数が正しく解釈されないことがあった * 存在しない履歴番号を "fc" 組込みのオペランドに指定した場合に 間違った範囲の履歴が処理されていた * "fc" 組込みで "fc -s foo=bar n" 形式の構文が使えていなかった * 対話シェルがジョブの状態を報告するときジョブの終了ステータスの 記録を消してしまっていた * 履歴をファイルから読み込む際起こり得る未定義動作を修正 * ${foo/bar/baz} 形式のパラメータ展開が POSIX 準拠モードで無効に なっていなかった * 引用符で囲まれた空の単語が単語分割で誤って削除されていた * パラメータ展開のインデックスでバックスラッシュによるクォートが 認識されていなかった ---------------------------------------------------------------------- Yash 2.40 = "unset" 組込みは "=" を含む変数名をエラーとせず無視するようにした = POSIX 準拠モードでは、閉じられていないヒアドキュメントをエラーと するようにした * メモリ確保時にメモリサイズがオーバーフローすると未定義動作に陥る 可能性があったのを修正 * 数式展開で浮動小数点数を代入する際にクラッシュしていた * $PATH 内に相対パスがあるとコマンド検索時にメモリリークすることが あったのを修正 * ${foo##bar*} で削除される文字列が最長マッチになっていなかった * ${foo%%*} が空文字列ではなく ${foo} に展開されてしまっていた * "getopts" 組込みで数字がオプションとして使えなかった * "command" 組込み経由で "." 組込みを実行し、スクリプトファイルが 見付からない場合、シェルを終了しないようにした * コマンド置換を含むが、コマンドの単語が空に展開される 単純コマンドの終了ステータスがコマンド置換の終了ステータスに なっていなかった * "exec" 組込みでスクリプトファイルが見付からないとききちんと 終了ステータス 127 で終了するようにした * コマンドの単語を展開する間、パイプが正しく接続されていなかった * "fg" および "bg" 組込みがジョブ名の出力に失敗した時はエラーとする ようにした ---------------------------------------------------------------------- Yash 2.39 + "read" 組込みの新しいオプション: -e, -P, -p + 補完スクリプトを追加: passwd, valgrind - configure の --disable-alias オプション = "read" 組込みで行編集を有効にするには -e (--line-editing) オプションを必要とするようにした = "trap" 組込みコマンドの出力が POSIX.1-2013 に準拠するように修正。 コマンド置換内で実行した場合、サブシェルではなく元のシェルの トラップを出力する。 = "return" 組込みコマンドが何からリターンすることができるのかに ついてマニュアルの記述を明確化し、動作も変更。"eval" および "fc" 組込みコマンドを "return" で中断できないようにした。 * 数式展開で "/=" および "%=" 演算子でのゼロ除算でクラッシュ * "complete" 組込みを -D オプション無しで呼んだ時クラッシュ * $HOME が設定されていないときにルートディレクトリから 初期化スクリプトを探して実行しようとしないように修正 * "read" 組込みを引数一つで呼び出したとき単語分割で先頭の IFS 空白類が残ってしまっていた * "read" 組込みの単語分割で、$IFS にバックスラッシュが含まれている 場合にバックスラッシュが正しく処理されていなかった * allexport オプションが有効な時でも "read" 組込みで配列に代入する ときエクスポートされていなかった * "read" 組込みで行編集を使って入力を待ち受ける間にトラップが実行 されてしまっていた * "set" 組込みの引数の補完を修正 * "git-svn" の補完スクリプトを修正 ---------------------------------------------------------------------- Yash 2.38 + 補完スクリプトを追加: git-rm = POSIX 準拠モードでは関数定義時にポータブルな関数名しか使えない ように変更 * 単語分割で IFS 文字の直後のバックスラッシュが欠ける問題を修正 * POSIX 準拠モードでは for ループでポータブルな変数名しか使えない ように修正 * チルダ展開の後に来る単語の補完を修正 * Git の引数の補完で、移動・コピーしたファイルの名前の補完を修正 * git-commit で非管理ファイルを補完しないように修正 ---------------------------------------------------------------------- Yash 2.37 + 補完スクリプトを更新: git-checkout (Git 2.1.2), git-push (Git 2.0.1), git-submodule (Git 2.1.2) ---------------------------------------------------------------------- Yash 2.36 + 補完スクリプトを追加: git-describe, tig + 補完スクリプトを更新: git-branch (Git 1.9.0) ---------------------------------------------------------------------- Yash 2.35 + '--traceall' オプション + 下記コマンドの補完スクリプトを追加: git-clean, git-grep, git-ls-remote, git-submodule, git-whatchanged + 下記コマンドの補完スクリプトを更新: git-cherry-pick, git-rebase (Git 1.8.1.4), ssh, ssh-add, ssh-keygen (OpenSSH 6.2) = -e オプションが有効な時にシェルを終了させる条件を POSIX.1-2008 の 2013 年版に合わせて変更 = "++" および "--" 演算子を POSIX 準拠モードで使えなくした = $RANDOM 変数は環境変数を無視して常に乱数を返すようにした = 履歴ファイルの中にとても長い行があるときでもそれ以降の履歴を 無視しないようにした = 非常に長いコマンドは履歴ファイルに保存しないようにした * 構文エラーメッセージを一部修正 * "git" と "ssh" と "rsync" の補完スクリプトを修正 ---------------------------------------------------------------------- Yash 2.34 + "test" 組込みの演算子追加: =~, -o (単項) + 下記コマンドの補完スクリプトを追加: configure, git-name-rev, git-request-pull, make, rsync = .yash_profile や .yashrc で構文エラーまたは展開エラーになった時 対話シェルでなくてもシェルを終了させないようにした = 行編集で、上書きした文字をバックスペースで戻せるようになった = 行編集で、バックスペースの度に undo 履歴を保存しないようにした * "git" の補完の修正 ---------------------------------------------------------------------- Yash 2.33 = "help" 組込みの出力を内蔵の説明文に変更 = コマンドの構文エラーに関する一部のエラーメッセージを変更 = いくつかの組込みコマンドで構文エラーをより厳密にチェックする ようになった * "set" 組込みの引数の構文が間違っているとき、POSIX の規定に従い シェルを終了させるように修正 ---------------------------------------------------------------------- Yash 2.32 + Man page および HTML マニュアル (英語および日本語) = "help" 組込みは man page の内容を出力するように変更 * "git" の補完の修正 ---------------------------------------------------------------------- Yash 2.31 + 拡張チルダ展開でディレクトリスタックのインデックスの補完に対応 + "complete" 組込みに --dirstack-index オプション追加 + "svn" バージョン 1.7 の補完に対応 * "git", "tar", "su" の補完の修正 ---------------------------------------------------------------------- Yash 2.30 * リダイレクトのファイル名補完を修正 + 下記コマンドの補完スクリプトを追加: svn, git, gitg, gitk, and gitx. * 補完スクリプトの細かい修正 ---------------------------------------------------------------------- Yash 2.29 + エイリアスの値に改行を入れられるようになった * 空の配列を "typeset" 組込みで表示しようとしたときに不正メモリ アクセスしていた * 関数の中身が (...) の形式の複合コマンドとして定義されているとき コマンドを正しくサブシェルで実行するように * 行編集で、undo コマンドは編集中の履歴項目でのみ使えるようにした ---------------------------------------------------------------------- Yash 2.28 = 対話シェルで、メールチェックはジョブ変化の報告の前に行うように なった * 数式展開で、評価すべからざる式を評価していることがあった * "array" 組込みで、-d オプション使用時に正と負のインデックスを混ぜ ると誤った要素を削除していた * 実行するコマンドが見付からなかったとき誤って EXIT トラップを実行 していた * 最後のコマンドがリダイレクトされていたとき EXIT トラップもそのリ ダイレクトで実行されてしまうことがあった * 配列の代入を伴う "exec" 組込みで配列の値を正しく export するよう にした * ${#@} が配列の要素の長さの配列に正しく展開されていなかった * 整数列へのブレース展開で無限ループすることがあった * "ssh" コマンドの補完で設定ファイルの読み込みを修正 * "ln", "mv" コマンドの補完で -T オプションに対応 * "trap" 組込みの補完で EXIT の補完に対応 ---------------------------------------------------------------------- Yash 2.27 * 細かいバグ修正 + 下記コマンドの補完スクリプトを追加: awk, chsh, gawk, nawk, pgawk, scp, sftp, ssh-add, ssh-agent, ssh-keygen, su, sudo, sudoedit, and useradd. * "set" 組込みと "tar" コマンドの補完スクリプトを修正 ---------------------------------------------------------------------- Yash 2.26 + "return" 組込みの新しいオプション: -n (--no-return) = 英語のエラーメッセージを書き直した = シェルオプションの名前の扱いを一般化した . オプション名の大文字・小文字を区別しなくなった . オプション名に含まれる英数字以外の文字を無視するようにした . オプション名に "no" を付けることで有効・無効を逆転できるように した = POSIX 準拠モードでは "case foo in (esac) bar; esac" をエラーに するようになった = 対話シェルでは noexec オプションを無視するようになった = 新着メール通知はメールボックスファイルが空のときは表示しないよう にした = "printf %c ''" は "printf %c" と同様に何も出力しなくなった = "complete" 組込みを -A または -R オプション付きで呼び出した場合、 -f オプションで生成されるファイル名候補についてはディレクトリ名 部分を除いてパターンマッチングを行うように * '=' を含む関数名を typeset および unset 組込みに指定できるように した。(-f オプション指定時) * SIGINT で中断された反復実行の終了ステータスを修正 + 下記コマンドの補完スクリプトを追加: bsdtar, eview, evim, gex, gnutar, gtar, return, rgview, rgvim, rview, rvim, slogin, ssh, tar, and which. * 下記コマンドの補完スクリプトの修正: bash, chmod, dash, less, mksh, set, sh, and umask. ---------------------------------------------------------------------- Yash 2.25 = 補完候補リストにおいてオプションは大文字小文字区別無くまとめる ようにした = 補完関数を実行中は $IFS 変数の値をデフォルトに戻すように + 下記コマンドの補完スクリプトを追加: bash, csplit, dash, diff, ed, egrep, env, ex, expand, fgrep, file, find, fold, getconf, grep, gview, gvim, gvimdiff, head, iconv, id, join, ksh, less, ln, locale, man, mesg, mkdir, mkfifo, mksh, more, mv, newgrp, nice, nl, nohup, od, paste, patch, pathchk, pr, ps, renice, rm, rmdir, sed, sh, sort, split, stty, tail, tee, time, touch, tr, uname, uniq, vi, view, vim, vimdiff, wc, who, xargs, and yash. * 下記コマンドの補完スクリプトの修正: cat, cd, chgrp, chmod, chown, cmp, comm, cp, crontab, cut, date, df, du, exec, ls, and set. ---------------------------------------------------------------------- Yash 2.24 + "." 組込みの新しいオプション: -L + "command" 組込みの新しいオプション: -a, -f, -k - "command" 組込みの廃止されたオプション: -B = FreeBSD で全ての種類のシグナルが扱えるように configure を修正 = 新しい補完メカニズム ("complete" 組込みの動作を変更。補完設定の $YASH_LOADPATH からの自動読み込み) + 下記コマンドの補完スクリプトを追加: basename, bg, cat, chgrp, chmod, chown, cmp, comm, command, cp, crontab, cut, date, df, du, export, popd, pushd, readonly, type, ".", and "[". = 下記コマンドの補完スクリプトを書き直し: alias, array, bindkey, break, cd, complete, continue, dirs, disown, echo, eval, exec, exit, fc, fg, getopts, hash, help, history, jobs, kill, ls, printf, pwd, read, set, suspend, test, trap, typeset, ulimit, umask, unalias, unset, and wait. - "return" 組込みの補完スクリプトを削除 = 補完候補リストで短いオプションが長いオプションの前に来るように 並び順を変更 * 存在しないファイルへのシンボリックリンクを正しく補完できるように * 補完機能のその他の修正 * リダイレクト内のパラメータ展開がエラーになったときに不正メモリ アクセスしていた ---------------------------------------------------------------------- Yash 2.23 * コマンドライン補完の途中ではトラップを実行しないようにした (予期せぬコマンドの実行により表示が乱れるのを防ぐため) * "-o notify" オプションが有効な時、候補生成関数を使用した補完の 最中にジョブの状態を出力しようとして不正メモリアクセスしていた * "-o verbose" オプションが有効な時でも、補完設定の自動読み込み時は 読み込んだ内容を出力しないようにした * "kill" 組込みの補完スクリプトを修正 ---------------------------------------------------------------------- Yash 2.22 + "test" 組込みの演算子追加: -G, -O, -N + $YASH_COMPPATH からの補完設定の自動読み込み * サブシェルが生成された直後にシグナルを受信したとき誤ってそれを 無視していたのを修正 * その他のバグ修正 + 下記コマンドの補完スクリプトを追加: alias, array, bindkey, break, cd, complete, continue, dirs, disown, echo, eval, exec, exit, fc, fg, getopts, hash, help, history, jobs, kill, ls, printf, pwd, read, return, set, suspend, test, trap, typeset, ulimit, umask, unalias, unset, wait. ---------------------------------------------------------------------- Yash 2.21 + 行編集での補完機能 = 対話シェルでは、それぞれのコマンドごとに $LINENO を 1 にリセット するようにした * "function" キーワードが "command -v" で認識されていなかった * 対話シェルが SIGINT を受け取った後の復旧処理を修正 * シェルへの入力を扱う関数を書き直し (いくつかのバグ修正含む) ---------------------------------------------------------------------- Yash 2.20 + "function" キーワードによる関数の定義 = "test" 組込みの "-nt", "-ot" 演算子による比較では存在しない ファイルをより古いとみなすようになった (Korn shell に倣う) = 行編集: いくつかの編集コマンドの名前を変更 vi-change-all -> vi-change-line vi-yank-and-change-all -> vi-yank-all-change-line vi-append-end -> vi-append-to-eol * 行編集: clear-and-redraw-all コマンドが正しく動いていなかった ---------------------------------------------------------------------- Yash 2.19 + FreeBSD でも "ulimit" 組込みが使えるようになった + メールチェックで、可能ならナノ秒単位でメールファイルをチェック するようにした + 行編集: 新しいコマンドの追加: oldest-history-bol, newest-history-bol, return-history-bol, prev-history-bol, next-history-bol, beginning-search-forward, beginning-search-backward = ファイル名パターンマッチングを高速化 = 行編集: 履歴検索中の accept-line コマンドは、検索結果が見つから ないときは失敗するようになった = 行編集: 以下のコマンドはカーソルを行頭に移動しないようになった oldest-history, newest-history, return-history, prev-history, next-history カーソルを行頭に移動する古い挙動は、名前が "-bol" で終わる 新しいコマンドによって得られる * 配列の代入のトレースの出力がおかしかったのを修正 * 行編集: vi-edit-and-accept コマンドでカウントを指定できるように * 行編集: emacs モードでの search-again-forward/backward コマンドの 後のカーソル位置を修正 ---------------------------------------------------------------------- Yash 2.18 + "test" 組込み: 文字列比較演算子追加 (==, ===, !==, <, <=, >, >=) + "test" 組込み: -nt, -ot 演算子で可能ならナノ秒単位でファイルの 更新日時を比較するように + "cd", "pushd" 組込みに --default-directory オプション追加 + "pushd" 組込みに --remove-duplicates オプション追加 + 右プロンプトとスタイラープロンプト + --le-alwaysrp オプション = $PS1 と $PS2 のフォント変更用エスケープシーケンスの解釈を変更 = $PS3 をデフォルトで初期化しないように = $PS4 でも $PS1/$PS2 と同様にエスケープシーケンスを解釈するように = "echo"/"printf" 組込みでナル文字を出力できるように * 行編集: $LINES と $COLUMNS が無視される場合があった * 行編集: いくつかの端末での文字の色を修正 ---------------------------------------------------------------------- Yash 2.17 + --le-visiblebell オプション + "test" 組込み: バージョン比較演算子 (-veq, -vne, -vgt, -vge, -vlt, -vle) + 行編集: "eof" コマンド = "typeset" 組込みをオペランドなしで実行したとき、-g オプションが なければローカル変数のみ表示するように = "typeset" 組込みで関数を表示させるときの出力を整形 = POSIX 準拠モードでないときは、コマンド置換やコマンドリダイレクト の中のコマンドもあらかじめ先に解析しておくようになった * パラメータ展開 "${#}" が構文エラーになっていた * サブシェルで "wait" 組込みを実行すると誤って親プロセスでない プロセスを待とうとすることがあった * 行編集の vi-replace-char コマンドが壊れていた * その他様々なバグ修正 ---------------------------------------------------------------------- Yash 2.16 + --curbg と --curstop オプション = --curasync オプションはデフォルトでオンに = 空の while/until ループの終了ステータスを変更 = "return" および "exit" 組込みで 256 以上の終了ステータスを使える ように = "echo" 組込みの八進数エスケープは \0 で始まるものしか解釈しない ように * 非対話シェルで "command" 組込みで特殊組込みを実行したとき構文 エラーになるとシェルが終了してしまっていたのを修正。 * EXIT トラップ設定時のシェルの終了ステータスを修正 * "read" と "getopts" 組込みで --allexport オプションが利いて いなかった * "getopts" 組込みのセグメンテーションフォルトその他エラー修正 * 行編集の vi-replace-char コマンドが不正メモリアクセスを 引き起こすことがあった * 行編集の emacs-just-one-space コマンドの結果がおかしかった ---------------------------------------------------------------------- Yash 2.15 + 対話シェルのコマンドを SIGINT で中断できるように + パス名展開でのブラケット記法に完全に対応した (libc の正規表現の 実装に依存) = シェルがセッションリーダーのとき "suspend" 組込みでサスペンドする には -f オプションを付けないといけないようにした = $YASH_LE_TIMEOUT 変数をデフォルトで設定するのをやめた = ジョブ制御有効なシェルがバックグラウンドで起動されたら停止する ように = ジョブ制御有効なシェルはデフォルトでは SIGTTOU を無視しないように = --nocaseglob がオンでも "${var#xxx}" などのパラメータ展開の マッチングでは大文字小文字を区別するように * リダイレクトにおけるパス名展開を修正 * 対話シェル起動時に無視されていたシグナルを "trap" 組込みで デフォルトのシグナルハンドラに変更できなかった * 組込みコマンドが誤ってシグナルに割り込まれるのを修正 * エイリアス置換の後に複合コマンドなどが正しく解析されるよう修正 * その他様々なバグ修正 ---------------------------------------------------------------------- Yash 2.14 + "bindkey" 組込みの -l オプション + 配列のインデックスに負数を指定できるようになった = "cd /.." の後 $PWD は /.. ではなく / にするように = "command -vb" はキーワード・エイリアス・関数を表示しないように = ほとんどの組込みにおいて、標準出力への書き込みがエラーになったら メッセージを吐いて非 0 で終了するようにした = "pwd", "times" 組込みに余計な引数を与えたときエラーにするように = POSIX 準拠モードでないとき特殊組込みでの変数代入がエラーに なってもシェルを終了しないように * 作業ディレクトリが / のとき "cd //" できなかった * yashrc の中で "bindkey" コマンドを使うと不正メモリアクセスが 発生していた * $PATH にない通常の組込みコマンドが "command -vb" で検索 できなかった * "dirs" 組込みに引数を与えたとき無限ループしていた * "ulimit" 組込みが -c, -d オプションを受け付けていなかった * POSIX 準拠モードでは "fg" 組込みは最大一つしか引数を受け付けない ように (ヘルプにはそう書いてあったが、実際にはそうなって いなかったのを修正) ---------------------------------------------------------------------- Yash 2.13 + "history" 組込み + "eval", "break", "continue" 組込みの -i オプション + コマンドが見つからないとき $COMMAND_NOT_FOUND_HANDLER 変数の値を 実行するように - --autocd オプション = 対話シェルで "return" 組込みが関数外で使われた時、終了ステータス 1 を返すように = コマンドの検索・実行を修正 * $PROMPT_COMMAND が設定されているとき、停止中のジョブがあると "exit" を二回やってもシェルを終了できなかった * $PROMPT_COMMAND が設定されていると "notifyle" オプションが正しく 動作していなかった * 行編集で redo がうまく動かなくなっていた * 行編集でトラップ処理のあとコマンドラインが再描画されていなかった * 行編集で vi-edit-and-accept コマンドについていくつかのバグ修正 ---------------------------------------------------------------------- Yash 2.12 + Emacs 風行編集 + "bindkey" 組込み + $PROMPT_COMMAND は配列でもよいように + 作業ディレクトリ変更後に $YASH_AFTER_CD を実行するように = "fg", "bg" 組込みは常に SIGCONT を対象ジョブに送信するように = "fg", "bg", "disown", "wait" 組込みの後で "exit" しようとしたとき も停止中のジョブに関する警告を出すように * vi 風行編集で、30 回目の yank の後誤ったテキストが put されていた * vi 風行編集で、カーソルが行頭にあると "s" コマンドが使えなかった ---------------------------------------------------------------------- Yash 2.11 + "--histspace", "--le-noconvmeta" オプション追加 + $HISTRMDUP 変数に対応 + $YASH_LE_TIMEOUT 変数に対応 + kill 組込みの -l オプションでシグナル名をオペランドとして 渡せるように = "--le-convmeta" オプションはオンオフ型オプションに = ログインシェルでは "$-" 特殊パラメータに "l" フラグを含めるように * 空の case 文 "case i in (*) esac" は常に終了ステータス 0 を返す ように * "-f" オプション有効時、クォートされた単語に余計なバックスラッシュ がくっついていた * vi 風行編集の v コマンドで不正メモリアクセスしていたのを修正 ---------------------------------------------------------------------- Yash 2.10 + 行編集におけるコマンド履歴検索 = 内容のない行は履歴に入れないように = vi 風行編集で、"cw" と "cW" が実際の vi と同様に振る舞うように * 行編集なしで configure したとき "notifyle" オプションを設定しよう とするとセグフォが発生していた * 行編集で、編集した履歴をアンドゥしたときカーソル位置が 正しくなかった * 行編集中の不正メモリアクセスのいくつかの修正 ---------------------------------------------------------------------- Yash 2.9 + 対話モードにおける行編集機能 x 行編集の実装がまだ途中 + 同じ履歴ファイルを使用する複数のシェルのインスタンス同士で 履歴を共有するように - "history" 組込みを廃止 = 非 ASCII なアルファベットも変数名に使えるように = 入れ子のパラメータ展開は必ずブレースでくくらないといけないように = "help" 組込みで、指定したコマンドが見つからなければ エラーを出すように * 数式展開における小数演算が正しくないことがあった * 数式展開で下線で始まる識別子がエラーになっていた * "${#" で始まるパラメータ展開の解析を修正 (例: "${#=x}") * "pwd" 組込みのヘルプメッセージを修正 ---------------------------------------------------------------------- Yash 2.8 + 変化量を指定できるブレース展開: {a..b..c} + "command" 組込みで -B, -b オプションが -v, -V オプションと同時に 使えるようになった = POSIX.1-2008 に準拠 . "read" 組込みは入力の末尾の空白類を常に削除するように . チルダ展開の結果を単語分割・パス名展開しないように . "pwd -P" で $PWD 変数を更新しないように . "cd -L foo/.." は、ディレクトリ "foo" が存在しないときエラーに . "command" 組込みで -p オプションが -v, -V オプションと同時に 使えるようになった . POSIX 準拠モードでは、ジョブ制御中でも非同期コマンドはすべて SIGINT と SIGQUIT を無視するように * fc 組込みで編集したコマンドを実行する際、$? 変数に fc で起動した エディタの終了ステータスが代入されてしまっていた * $IFS の中にバックスラッシュ、コンマ、またはブレースを入れても 正しく単語分割できなかった * パス名展開において、ディレクトリの検索権限だけが必要な時でも、 読み込み権限がなければ展開に失敗していた * シェルが起動するコマンドのシグナルマスクは、シェルのマスクを 受け継ぐようにした (トラップしたシグナルのマスクを除く) * "autocd" オプション有効時 "command" 組込みにコマンドとして ディレクトリ名を与えたときの動作を修正 ---------------------------------------------------------------------- Yash 2.7 + ${array[index]:=value} で配列要素への代入が可能に + ヒアストリングのための新しいリダイレクト演算子 "<<<" + パイプを開く新しいリダイレクト演算子 ">>|" - ループパイプ機能廃止 = シェルの起動時に $IFS 変数を常にデフォルト値で初期化するように * "echo" および "printf" 組込みでエラー時にメッセージを出すように * ファイル名の先頭にあるクォートされたピリオドがファイル名展開で 正しくマッチしていなかった ---------------------------------------------------------------------- Yash 2.6 + fc 組込みに -q オプション追加 + 複合コマンドの内容は空でもよいように * "alias -p" でハイフンを適切にエスケープするように * POSIX 準拠モードの for 文で、識別子の直後のセミコロンをエラーに するように * 複合コマンドの直後でグローバルエイリアスが使えなかった * "-o notify" オプションが有効な時 "fg" および "wait" 組込みが 不正メモリアクセスを引き起こしていた ---------------------------------------------------------------------- Yash 2.5 = シェルが使用している FD のリダイレクトはエラーにするように * いくつかのリダイレクト構文エラーを見逃していた * "sig.y" テストが間違っていた * リダイレクト付きの "exec" コマンドがリダイレクト付きの { } 括弧の 中にある場合、括弧に対するリダイレクトが残ったままになっていた * for 文の識別子の直後のコメントの解析エラーを修正 ---------------------------------------------------------------------- Yash 2.4 = "jobs" 組込みの -p オプションに対応する長いオプションを --pid-only から --pgid-only に変更 * SIGCHLD, SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTOU のトラップが 誤って無視されることがあった * SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTOU のシグナルハンドラが 誤って解除されている瞬間があった * "wait" 組込みがシグナルに割り込まれたときの終了ステータスを修正 * ファイルアクセス権限の判定を修正 * "command -V xxx/yyy" で "xxx/yyy" が正しいコマンドでないとき エラーメッセージを出すように ---------------------------------------------------------------------- Yash 2.3 = 対話的かつ非 POSIX 準拠モードで動作時、LC_CTYPE の変更が直ちに 有効になるように * パラメータ展開で空文字列を正しく展開できていなかった * 稀にシグナルが無視される競合状態を修正 * typeset/readonly/export 組込みで代入を行うとシェル内部のデータが 正しく更新されていなかった。これにより、PATH を変更した後も古い PATH を使い続けてしまうなどの問題があった。 ---------------------------------------------------------------------- Yash 2.2 + "help", "pushd", "popd", "dirs" 組込み = ルートディレクトリから相対パスで "cd" するとき設定される $PWD の値を修正 * "." や ".." を含むファイル名展開が正しく動作していなかった ---------------------------------------------------------------------- Yash 2.1 + 配列変数 + "array", "echo", "printf", "test" 組込み + "read" 組込みの -A オプション * "${FOO:-bar}" のようなパラメータ展開の中のコロンフラグが パラメータの値が特定の文字で始まる場合に無視されていた * コマンドに対して代入するとき読み取り専用変数でも代入できて しまっていた * "unset" 組込みに変数名として "=" を渡すと誤って位置パラメータを 削除し、場合によっては不正なメモリアクセスを引き起こしていた。 * 空白のみからなる変数の値のフィールド分割が誤っていた * ファイルアクセス権限の確認は実ユーザ・グループ ID ではなく実効 ユーザ・グループ ID に基づいて行うように ---------------------------------------------------------------------- Yash 2.0 + "history" 組込み + コマンドリダイレクト * POSIX 準拠モードの非対話シェルは特殊組込みの特定のエラーで終了 するように * エイリアス置換の処理を改良 * その他のバグ修正 ---------------------------------------------------------------------- Yash 2.0 beta2 + コマンド履歴 + "type", "hash", "fc" 組込み = "command" 組込みの -V オプションでコマンドがみつからなかった場合 メッセージを出すように * $PATH に代入しながら外部コマンドを呼出すと不正なメモリアクセスが 発生していた。 * 一重引用符が正しく解析されない場合があった。 * その他のバグ修正 ---------------------------------------------------------------------- Yash 2.0 beta1 + "read", "getopts", "command" 組込み + --autocd, --curasync オプション + メールチェック機能 + プロンプトコマンド = "readonly", "export" 組込みは常にグローバルに * 標準入力をバッファリングしないようにした * "typeset" 組込みで '=' 以外の全ての文字を変数名として受け付ける ようにした * 特殊パラメータ $? が変数代入内で行われたコマンド置換の終了 ステータスを反映するように * その他様々なバグ修正 ---------------------------------------------------------------------- Yash 2.0 beta0 + "eval", "exec", ".", "times", "umask", "typeset", "export", "readonly", "unset", "shift", "trap" 組込み + "wait" 組込みを中断できるように + 数式展開 + 対話モードでプロセスがシグナルによって終了したらメッセージを 表示するように - 起動時に "/etc/profile" と "~/.profile" は読み込まないように * エイリアス置換でのトークン区切りの認識を修正 * 値のない代入でのトークン区切りの認識を修正 * その他様々なバグ修正 ---------------------------------------------------------------------- Yash 2.0 alpha2 + 起動時に /etc/profile や ~/.yashrc を読み込むように + --noprofile, --norcfile, --rcfile オプション + "pwd", "set", "exit", "return", "break", "continue", "jobs", "fg", "bg", "wait", "disown", "alias", "unalias" 組込み * --nocaseglob が -c として解釈されていた * --nocaseglob で大文字小文字の認識が不十分だった * ジョブ制御がオフなら、対話シェルでもジョブの状態変化を プロンプト前に表示しないようにした = --nounset 有効時、${i?xxx} はエラーとして xxx を返すように = ヘルプの表示を簡潔に ---------------------------------------------------------------------- Yash 2.0 alpha1 + オプション追加: -x, -h, -a = POSIX 準拠モードでは 'PS3' のデフォルト値を設定しないように + 'configure' に新しい '--no-undefined' オプション追加 + ":", "true", "false", "cd" 組込みコマンド * PATH の値が変更されるときコマンド名ハッシュをクリアするように * ナル文字が入力された時のアサーションエラー * その他様々なバグ修正 ---------------------------------------------------------------------- Yash 2.0 alpha0 (2.x 版の最初のリリース) x 数式展開が未実装 x 組込みコマンドが全く未実装 yash-2.49/README000066400000000000000000000137571354143602500133140ustar00rootroot00000000000000Yash: yet another shell http://yash.osdn.jp/ ======================= Yash, yet another shell, is a POSIX-compliant command line shell written in C99 (ISO/IEC 9899:1999). Yash is intended to be the most POSIX-compliant shell in the world while supporting features for daily interactive and scripting use. Notable features are: * Global aliases * Arrays * Socket redirection, pipeline redirection, and process redirection * Brace expansion and extended globbing * Fractional numbers in arithmetic expansion * Prompt command and command-not-found handler * Command line completion with predefined completion scripts for more than 100 commands * Command line prediction based on command history Yash can be modified/redistributed under the terms of the GNU General Public License (Version 2) but the use of this program is without any warranty. See the "COPYING" file for the full text of GPL. Yash is an OSDN-hosted project. See also the project summary page at . ===== Current Development Status ===== Yash now fully supports POSIX.1-2008 (IEEE Std 1003.1, 2016 Edition) except for the limitations listed below. Yash is stable. A maintenance update is released every three months or so. For the change history, see the "NEWS" file. ===== Requirements ===== Yash is supposed to build and run on any POSIX.1-2001 environment with the Software Development Utilities and the C-Language Development Utilities options. Currently, yash is mainly tested on Fedora, macOS, and Cygwin. ===== Installation ===== See the "INSTALL" file to see how to build and install yash. After installation, the manual can be viewed by $ man yash The manual is also available online at . ===== Basic Configuration ===== Below is a description of basic configuration that you might want to see after installation to get started with yash. For configuration details, see the manual. --- Initialization scripts --- When yash is started as a login shell, it reads ~/.yash_profile. This file is a shell script in which you define environment variables using the export command. When yash is started for an interactive use, it reads ~/.yashrc (after reading ~/.yash_profile if it is a login shell also). In this file, you make other configurations such as aliases, prompt strings, key bindings for command line editing, and command-not-found handler. Use the "share/initialization/sample" file as a template for your ~/.yashrc. --- Making yash your login shell --- In many Unix-like OSes, a shell must be listed in /etc/shells to be set as a login shell. Edit this file and ensure that the path to yash is written in the file. Then, run the "chsh" command in the terminal and follow instructions from the command. Depending on your system, you may have to use another command to change the login shell. See documentation on your system. ===== Implementation Notes ===== * In C, a null character represents the end of a string. If input to the shell itself contains a null character, characters following the null character will be ignored. * We assume that an overflow in signed integer arithmetic or type conversion silently yields an implementation-defined integer value rather than resulting in an error. * The GCC extension keyword `__attribute__' is used in the source code. When not compiled with GCC, this keyword is removed by the preprocessor, so generally there is no harm. But if your compiler uses this keyword for any other purpose, compilation may fail. Additionally, some other identifiers starting with '_' may cause compilation errors on some rare environments. * Some signals are assumed to have the specific numbers: SIGHUP=1 SIGINT=2 SIGQUIT=3 SIGABRT=6 SIGKILL=9 SIGALRM=14 SIGTERM=15 * POSIX disallows non-interactive shells to ignore or catch SIGTTIN, SIGTTOU, and SIGTSTP by default. Yash, however, ignores these signals if job-control is enabled, even if non-interactive. * File permission flags are assumed to have the specific values: 0400=user read 0200=user write 0100=user execute 0040=group read 0020=group write 0010=group execute 0004=other read 0002=other write 0001=other execute * The character categorization in locales other than the POSIX locale is assumed upward compatible with the POSIX locale. * The -o nolog option is not supported: it is silently ignored. * According to POSIX, the value of variable `PS1' is subject to parameter expansion. Yash performs command substitution and arithmetic expansion as well on the `PS1' value. * According to POSIX, the command `printf %c foo' should print the first byte of string `foo'. Yash prints the first character of `foo', which may be more than one byte. * The "return" built-in, if executed in a trap, can operate only on a function, script, or loop that has been executed within the trap. This limitation is not strictly POSIX-compliant, but needed for consistent and predictable behavior of the shell. * Results of pathname expansion is sorted only by collating sequence of the current locale. If the collating sequence does not have a total ordering of characters, order of uncomparable results are unstable. This limitation is not strictly POSIX-compliant, but inevitable due to use of wide characters in the whole shell. ===== Known Issues ===== * Line number ($LINENO) may not be counted correctly in and after a complex expansion containing a line continuation. * Non-ASCII characters may not be correctly handled in some locales on Solaris. This may be worked around by undefining the HAVE_WCSNRTOMBS macro in the config.h header file. ===== Contributions ===== Comments, suggestions, and bug reports are welcome at: * Issue tracking system * Discussion forum If you are interested in translation, please refer to . ====================== Watanabe, Yuki yash-2.49/README.ja000066400000000000000000000203021354143602500136650ustar00rootroot00000000000000Yash: yet another shell http://yash.osdn.jp/ ======================= このプログラムは C 言語 (C99: ISO/IEC 9899:1999, JIS X 3010:2003) で 書いた Unix 系 OS 用コマンドライン端末シェルです。Yash は世界で最も 正確な POSIX 規格準拠を目指しながらも、対話シェルおよびシェルスクリプト 実行プログラムとして便利な機能を備えています。 * グローバルエイリアス * 配列 * ソケットリダイレクト・パイプリダイレクト・プロセスリダイレクト * ブレース展開・拡張パス名展開 * 数式展開での小数の計算 * プロンプトコマンド * コマンドが見付からないときのハンドラー * コマンドライン補完 (100 個以上のコマンドに対する補完設定付き) * コマンド履歴に基づくコマンドラインの推定 このプログラムは GNU General Public License (Version 2) の元で自由に 再配布・変更などができます。その代わり、プログラムの利用は全て各自の 自己責任の下で行っていただくことになります。作者はプログラムの瑕疵に 対して一切責任を取りません。ライセンスの本文については "COPYING" ファイルを参照してください。 このプログラムは OSDN のプロジェクトの一つとして開発されています。 プロジェクト概要ページは にあります。 ===== 開発状況 ===== Yash は既に POSIX.1-2008 (IEEE Std 1003.1, 2016 Edition) を全てサポート しています (ただし下記制限事項を除く)。 Yash は安定しています。バグ修正のためのバージョンアップはおおよそ三箇月 に一度リリースされます。変更履歴は "NEWS.ja" ファイルにあります。 ===== 要件 ===== Software Development Utilities オプションと C-Language Development Utilities オプションを備えた任意の POSIX.1-2001 環境でビルド・実行 できるはずです。 今のところ yash の動作は主に Fedora と macOS と Cygwin で検証されて います。 ===== インストール ===== Yash をビルドしインストールする方法については "INSTALL.ja" ファイルを 参照してください。 インストール後、マニュアルは $ man yash で参照できます。また でオンラインで読む こともできます。 ===== 基本設定 ===== 以下は、yash を使い始めるにあたって有用な設定の手引きです。設定の詳細は マニュアルを参照してください。 --- 初期化スクリプト --- Yash がログインシェルとして起動されたとき、~/.yash_profile を読み込み ます。このファイルはシェルスクリプトとして実行されます。この中で export コマンドを使うことで環境変数を設定できます。 Yash が対話シェルとして起動されたとき、(~/.yash_profile も読み込む場合 はその後で) ~/.yashrc を読み込みます。この中でエイリアス、プロンプト、 コマンド行編集のキーバインド設定、コマンドが見付からないときのハンドラ ー等を設定します。サンプルとして "share/initialization/sample" ファイルを参考に自分用の ~/.yashrc を設定してください。 --- Yash をログインシェルにする --- 多くの Unix 系 OS では、ログインシェルとして使用可能なシェルは /etc/shells ファイルに名前が挙げられている必要があります。このファイル を編集し、yash へのパスがファイルに含まれているようにしてください。 その後、端末で "chsh" コマンドを実行してログインシェルを yash に変更し てください。環境によってはこれ以外の方法でログインシェルを設定するよう になっているかもしれません。詳しくはお使いの環境のマニュアルを参照くだ さい。 ===== 実装上の注意事項 ===== * C 言語では、ナル文字は文字列の終わりを表します。そのため、入力した ファイルの内容や文字列にナル文字が含まれているとそれ以降の部分が正 しく処理されなくなってしまいます。 * 符号付き整数の演算・型変換においてオーバーフローが発生した場合でも エラーにならずに (処理系定義の) 何らかの整数値が返されることを仮定 しています。 * ソース内の随所で GCC の拡張機能である __attribute__ キーワードを使 用しています。GCC 以外の環境ではプリプロセッサによって消去するよう にしていますが、__attribute__ 識別子を他の用途で使用している処理系 ではコンパイルに支障をきたすかもしれません。他にもいくつか '_' で始 まる識別子を使用しているため、非常に特殊な環境では正しくコンパイル できないかもしれません。 * いくつかのシグナルは特定の番号を持っていると仮定しています: SIGHUP=1 SIGINT=2 SIGQUIT=3 SIGABRT=6 SIGKILL=9 SIGALRM=14 SIGTERM=15 * POSIX は対話的でないシェルが SIGTTIN, SIGTTOU, SIGTSTP をデフォルト で無視・キャッチすることを禁じています。しかし yash はジョブ制御が 有効な時は対話的でなくてもこれらのシグナルを無視します。 * ファイルアクセス権フラグは特定の値を持っていると仮定しています: 0400=user read 0200=user write 0100=user execute 0040=group read 0020=group write 0010=group execute 0004=other read 0002=other write 0001=other execute * POSIX ロケール以外のロケールでの文字クラスの分類は POSIX ロケールで の分類と上位互換であると仮定しています。 * -o nolog オプションには対応していません。(無視されます) * POSIX によると、PS1 変数の値はパラメータ展開されることになっていま す。Yash ではさらに PS1 変数の値に対してコマンド置換と数式展開も行 います。これは実装上の都合によるものです。 * POSIX によると、コマンド `printf %c foo' は文字列 `foo' の最初のバ イトを出力することになっています。Yash では `foo' の最初の文字を出 力するので、出力が複数バイトになることがあります。 * トラップの中で return 組込みコマンドを実行しても、トラップの外で 実行されている関数やスクリプトを中断・続行させることはできません。 この挙動は厳密には POSIX に従っていませんが、予測可能で一貫性のある 動作を保つためにこのようになっています。 * パス名展開の結果はその時のロケールでの辞書順でソートされます。 ロケールの順序定義が全順序でない場合、比較不可能な結果同士の順序は 不定です。この挙動は厳密には POSIX に従っていませんが、POSIX ロケールでのバイト単位再比較がワイド文字では不可能なためこのように なっています。 ===== 既知の問題 ===== * 行連結を含む複雑な展開があると行番号 ($LINENO) が正しく計算されない ことがあります。 * Solaris では一部のロケールで非 ASCII 文字が正しく扱われないことが あります。config.h ヘッダーファイルで HAVE_WCSNRTOMBS マクロの定義 を削除してビルドすると問題を回避できるかもしれません。 ===== お知らせ ===== 感想・提案・バグ報告等を歓迎します。ただし、全ての提案やバグ報告に 応じることを約束するものではありません。 * 課題管理システム * 掲示板 翻訳に興味がおありの場合は以下のページをご覧ください ====================== 渡邊裕貴 yash-2.49/THANKS000066400000000000000000000003661354143602500133370ustar00rootroot00000000000000The author thanks: * Sergey Romanov, who provided a great patch file and suggestion to fix many errors in the documentation * Martijn Dekker, who pointed out many long-hidden bugs * Adrian Perez, for implementing the le-predict-empty option yash-2.49/alias.c000066400000000000000000000374351354143602500136700ustar00rootroot00000000000000/* Yash: yet another shell */ /* alias.c: alias substitution */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "alias.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "builtin.h" #include "exec.h" #include "expand.h" #include "hashtable.h" #include "option.h" #include "parser.h" #include "plist.h" #include "refcount.h" #include "strbuf.h" #include "util.h" #if YASH_ENABLE_LINEEDIT # include "xfnmatch.h" # include "lineedit/complete.h" #endif typedef struct alias_T { bool isglobal; refcount_T refcount; size_t valuelen; /* length of `value' */ wchar_t value[]; } alias_T; typedef struct aliaslist_T { struct aliaslist_T *next; alias_T *alias; size_t limitindex; } aliaslist_T; /* An alias list created of the `aliaslist_T' structure is used to prevent * infinite recursive substitution of an alias. When an alias is substituted, * the alias and the end index of the substituted string are saved in the list. * Substitution of the same alias is not performed before the saved index, * thus preventing recursive substitution. * `aliaslist_T' is also used to indicate which command words are subject to * substitution after another substitution that ends with a blank. * Alias list items are sorted in the order of `limitindex'. */ static bool is_alias_name_char(wchar_t c) __attribute__((pure)); static void free_alias(alias_T *alias); static inline void vfreealias(kvpair_T kv); static void define_alias( const wchar_t *nameandvalue, const wchar_t *equal, bool global) __attribute__((nonnull)); static bool remove_alias(const wchar_t *name) __attribute__((nonnull)); static void remove_all_aliases(void); static bool contained_in_list( const aliaslist_T *list, const alias_T *alias, size_t i) __attribute__((pure)); static void add_to_aliaslist( aliaslist_T **list, alias_T *alias, size_t limitindex) __attribute__((nonnull)); static bool remove_expired_aliases( aliaslist_T **list, size_t index, const xwcsbuf_T *buf) __attribute__((nonnull)); static bool is_after_blank(size_t i, size_t j, const xwcsbuf_T *buf) __attribute__((nonnull)); static bool is_redir_fd(const wchar_t *s) __attribute__((nonnull,pure)); static bool print_alias(const wchar_t *name, const alias_T *alias, bool prefix); /* Hashtable mapping alias names (wide strings) to alias_T's. */ hashtable_T aliases; /* Initializes the alias module. */ void init_alias(void) { assert(aliases.capacity == 0); ht_init(&aliases, hashwcs, htwcscmp); } /* Returns true iff `c' is a character that can be used in an alias name. */ bool is_alias_name_char(wchar_t c) { return !wcschr(L" \t\n=$<>\\'\"`;&|()#", c) && !iswblank(c); } /* Decreases the reference count of `alias' and, if the count becomes zero, * frees it. This function does nothing if `alias' is a null pointer. */ void free_alias(alias_T *alias) { if (alias != NULL) if (refcount_decrement(&alias->refcount)) free(alias); } /* Applies `free_alias' to the value of key-value pair `kv'. */ void vfreealias(kvpair_T kv) { free_alias(kv.value); } /* Defines an alias. * `nameandvalue' must be a wide string of the form "name=value" and `equal' * must point to the first L'=' in `nameandvalue'. * This function doesn't check if the name and the value are valid. */ void define_alias( const wchar_t *nameandvalue, const wchar_t *equal, bool global) { assert(wcschr(nameandvalue, L'=') == equal); size_t namelen = equal - nameandvalue; size_t valuelen = wcslen(equal + 1); alias_T *alias = xmallocs(sizeof *alias, add(add(namelen, valuelen), 2), sizeof *alias->value); alias->isglobal = global; alias->refcount = 1; alias->valuelen = valuelen; wmemcpy(alias->value, equal + 1, valuelen); alias->value[valuelen] = L'\0'; wmemcpy(alias->value + valuelen + 1, nameandvalue, namelen); alias->value[namelen + valuelen + 1] = L'\0'; vfreealias(ht_set(&aliases, alias->value + valuelen + 1, alias)); } /* Removes the alias definition with the specified name if any. * Returns true if the alias definition is successfully removed. * Returns false if no alias definition is found to be removed. */ bool remove_alias(const wchar_t *name) { alias_T *alias = ht_remove(&aliases, name).value; if (alias != NULL) { free_alias(alias); return true; } else { return false; } } /* Removes all alias definitions. */ void remove_all_aliases(void) { ht_clear(&aliases, vfreealias); } /* Returns the value of the specified alias (or null if there is no such). */ const wchar_t *get_alias_value(const wchar_t *aliasname) { const alias_T *alias = ht_get(&aliases, aliasname).value; if (alias != NULL) return alias->value; else return NULL; } /* Frees the specified alias list and its contents. */ void destroy_aliaslist(aliaslist_T *list) { while (list != NULL) { aliaslist_T *next = list->next; free_alias(list->alias); free(list); list = next; } } /* Checks if the specified alias list contains the specified alias. * List items whose limit index is less than `i' are ignored. */ bool contained_in_list(const aliaslist_T *list, const alias_T *alias, size_t i) { while (list != NULL) { if (list->limitindex >= i && list->alias == alias) return true; list = list->next; } return false; } /* Adds an alias to an alias list with the specified limit index. */ void add_to_aliaslist(aliaslist_T **list, alias_T *alias, size_t limitindex) { /* Find where to insert the new item. Remember, the list is sorted in the * order of `limitindex'. */ while (*list != NULL && (*list)->limitindex < limitindex) list = &(*list)->next; aliaslist_T *newelem = xmalloc(sizeof *newelem); newelem->next = *list; newelem->alias = alias; refcount_increment(&newelem->alias->refcount); newelem->limitindex = limitindex; *list = newelem; } /* Removes items that are no longer significant. An item is significant if (1) * its limit index is larger than `index', or (2) it is a non-global alias and * all the characters are blank between the indexes `limitindex-1' and `index'. * Returns true iff a significant item of type (2) is left. */ /* If this function returns true, the `index' is just after the result of alias * substitution that ends with a blank, in which case the next word should be * checked for another substitution. */ bool remove_expired_aliases( aliaslist_T **list, size_t index, const xwcsbuf_T *buf) { aliaslist_T *item = *list; bool afterblank = false; /* List items are ordered by index; we don't have to check all the items. */ while (item != NULL && item->limitindex <= index) { if (!item->alias->isglobal && is_after_blank(item->limitindex, index, buf)) { afterblank = true; break; } aliaslist_T *next = item->next; free_alias(item->alias); free(item); item = next; } *list = item; return afterblank; } /* Tests if the character just before `i` in `buf' is a blank and all the * characters between `i' and `j' are blanks. */ bool is_after_blank(size_t i, size_t j, const xwcsbuf_T *buf) { assert(i <= j); assert(j <= buf->length); if (i == 0) return false; for (i--; i < j; i++) if (!iswblank(buf->contents[i])) return false; return true; } /* Increases the limit index by `inc' for each item whose index is larger * than `i'. */ void shift_aliaslist_index(aliaslist_T *list, size_t i, ptrdiff_t inc) { while (list != NULL) { if (list->limitindex > i) { assert(inc >= 0 || (size_t) -inc <= list->limitindex); list->limitindex += inc; } list = list->next; } } /* Performs alias substitution on the word starting at index `i' in buffer * `buf'. */ bool substitute_alias(xwcsbuf_T *restrict buf, size_t i, aliaslist_T **restrict list, substaliasflags_T flags) { if (is_redir_fd(&buf->contents[i])) return false; size_t j = i; while (is_alias_name_char(buf->contents[j])) j++; if (!is_token_delimiter_char(buf->contents[j])) return false; return substitute_alias_range(buf, i, j, list, flags); } /* Performs alias substitution on the word in the index range `[i, j)` in buffer * `buf'. * If AF_NONGLOBAL is not in `flags' and `i' is not after another substitution * that ends with a blank, only global aliases are substituted. * The substitution is not recursive: the resultant string may be another alias * that should be substituted by calling `substitute_alias' again. * Returns true iff any alias was substituted. */ bool substitute_alias_range(xwcsbuf_T *restrict buf, size_t i, size_t j, aliaslist_T **restrict list, substaliasflags_T flags) { if (aliases.count == 0) return false; if (remove_expired_aliases(list, i, buf)) flags |= AF_NONGLOBAL; if (!(flags & AF_NONGLOBAL) && posixly_correct) return false; if (i >= j) return false; if (flags & AF_NOEOF) if (j == buf->length) return false; alias_T *alias; /* get alias definition */ wchar_t savechar = buf->contents[j]; buf->contents[j] = L'\0'; alias = ht_get(&aliases, buf->contents + i).value; buf->contents[j] = savechar; /* check if we should do substitution */ if (alias == NULL) return false; if (!(flags & AF_NONGLOBAL) && !alias->isglobal) return false; if (contained_in_list(*list, alias, i)) return false; /* do substitution */ wb_replace_force(buf, i, j - i, alias->value, alias->valuelen); shift_aliaslist_index( *list, i, (ptrdiff_t) alias->valuelen - (ptrdiff_t) (j - i)); /* add the alias to the list to track recursion */ add_to_aliaslist(list, alias, i + alias->valuelen); return true; } /* Returns true iff the specified string starts with any number of digits * followed by L'<' or L'>'. */ /* An IO_NUMBER token, which specifies the file descriptor a redirection * affects, must not be alias-substituted. This function check if the word is * such a token. */ bool is_redir_fd(const wchar_t *s) { while (iswdigit(*s)) s++; return *s == L'<' || *s == L'>'; } /* Prints an alias definition to the standard output. * On error, an error message is printed to the standard error. * Returns true iff successful. */ bool print_alias(const wchar_t *name, const alias_T *alias, bool prefix) { wchar_t *qvalue = quote_as_word(alias->value); const char *format; bool success; if (!prefix) format = "%ls=%ls\n"; else if (alias->isglobal) if (name[0] == L'-') format = "alias -g -- %ls=%ls\n"; else format = "alias -g %ls=%ls\n"; else if (name[0] == L'-') format = "alias -- %ls=%ls\n"; else format = "alias %ls=%ls\n"; success = xprintf(format, name, qvalue); free(qvalue); return success; } /* Prints an alias definition to the standard output if defined. * This function is used in the "command" built-in. * Returns true iff a non-global alias had been defined for the specified name. */ bool print_alias_if_defined(const wchar_t *aliasname, bool user_friendly) { const alias_T *alias = ht_get(&aliases, aliasname).value; if (alias == NULL || alias->isglobal) return false; if (!user_friendly) print_alias(aliasname, alias, true); else xprintf(gt("%ls: an alias for `%ls'\n"), aliasname, alias->value); return true; } #if YASH_ENABLE_LINEEDIT /* Generates candidates to complete an alias matching the pattern. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_alias_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_ALIAS)) return; le_compdebug("adding alias name candidates"); if (!le_compile_cpatterns(compopt)) return; size_t i = 0; kvpair_T kv; while ((kv = ht_next(&aliases, &i)).key != NULL) { const alias_T *alias = kv.value; le_candgentype_T type = alias->isglobal ? CGT_GALIAS : CGT_NALIAS; if (compopt->type & type) if (le_wmatch_comppatterns(compopt, kv.key)) le_new_candidate(CT_ALIAS, xwcsdup(kv.key), NULL, compopt); } } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-ins **********/ /* Options for the "alias" built-in. */ const struct xgetopt_T alias_options[] = { { L'g', L"global", OPTARG_NONE, false, NULL, }, { L'p', L"prefix", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "alias" built-in, which accepts the following options: * -g: define global aliases * -p: print aliases in the form of whole commands */ int alias_builtin(int argc, void **argv) { bool global = false, prefix = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, alias_options, 0)) != NULL) { switch (opt->shortopt) { case L'g': global = true; break; case L'p': prefix = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (xoptind == argc) { /* print all aliases */ kvpair_T *kvs = ht_tokvarray(&aliases); qsort(kvs, aliases.count, sizeof *kvs, keywcscoll); for (size_t i = 0; i < aliases.count; i++) { print_alias(kvs[i].key, kvs[i].value, prefix); if (yash_error_message_count > 0) break; } free(kvs); } else { /* define or print aliases */ do { wchar_t *arg = ARGV(xoptind); wchar_t *nameend = arg; while (is_alias_name_char(*nameend)) nameend++; if (nameend != arg && *nameend == L'=') { /* define alias */ define_alias(arg, nameend, global); } else if (nameend != arg && *nameend == L'\0') { /* print alias */ const alias_T *alias = ht_get(&aliases, arg).value; if (alias != NULL) { if (!print_alias(arg, alias, prefix)) break; } else { xerror(0, Ngt("no such alias `%ls'"), arg); } } else { xerror(0, Ngt("`%ls' is not a valid alias name"), arg); } } while (++xoptind < argc); } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if YASH_ENABLE_HELP const char alias_help[] = Ngt( "define or print aliases" ); const char alias_syntax[] = Ngt( "\talias [-gp] [name[=value]...]\n" ); #endif /* The "unalias" built-in, which accepts the following option: * -a: remove all aliases */ int unalias_builtin(int argc, void **argv) { bool all = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, all_help_options, 0)) != NULL) { switch (opt->shortopt) { case L'a': all = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (all) { if (xoptind != argc) return too_many_operands_error(0); remove_all_aliases(); } else { if (xoptind == argc) return insufficient_operands_error(1); do { const wchar_t *arg = ARGV(xoptind); if (!remove_alias(arg)) xerror(0, Ngt("no such alias `%ls'"), arg); } while (++xoptind < argc); } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if YASH_ENABLE_HELP const char unalias_help[] = Ngt( "undefine aliases" ); const char unalias_syntax[] = Ngt( "\tunalias name...\n" "\tunalias -a\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/alias.h000066400000000000000000000042121354143602500136600ustar00rootroot00000000000000/* Yash: yet another shell */ /* alias.h: alias substitution */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_ALIAS_H #define YASH_ALIAS_H #include #include "xgetopt.h" struct xwcsbuf_T; struct aliaslist_T; typedef enum { AF_NONGLOBAL = 1 << 0, AF_NOEOF = 1 << 1, } substaliasflags_T; extern void init_alias(void); extern const wchar_t *get_alias_value(const wchar_t *aliasname) __attribute__((nonnull,pure)); extern void destroy_aliaslist(struct aliaslist_T *list); extern void shift_aliaslist_index( struct aliaslist_T *list, size_t i, ptrdiff_t inc); extern _Bool substitute_alias( struct xwcsbuf_T *restrict buf, size_t i, struct aliaslist_T **restrict list, substaliasflags_T flags) __attribute__((nonnull)); extern _Bool substitute_alias_range( struct xwcsbuf_T *restrict buf, size_t i, size_t j, struct aliaslist_T **restrict list, substaliasflags_T flags) __attribute__((nonnull)); extern _Bool print_alias_if_defined( const wchar_t *aliasname, _Bool user_friendly) __attribute__((nonnull)); extern int alias_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char alias_help[], alias_syntax[]; #endif extern const struct xgetopt_T alias_options[]; extern int unalias_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char unalias_help[], unalias_syntax[]; #endif extern const struct xgetopt_T unalias_options[]; #endif /* YASH_ALIAS_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/arith.c000066400000000000000000001111611354143602500136730ustar00rootroot00000000000000/* Yash: yet another shell */ /* arith.c: arithmetic expansion */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "arith.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "option.h" #include "strbuf.h" #include "util.h" #include "variable.h" typedef struct word_T { const wchar_t *contents; size_t length; } word_T; typedef enum valuetype_T { VT_INVALID, VT_LONG, VT_DOUBLE, VT_VAR, } valuetype_T; typedef struct value_T { valuetype_T type; union { long longvalue; double doublevalue; word_T varvalue; } value; } value_T; #define v_long value.longvalue #define v_double value.doublevalue #define v_var value.varvalue typedef enum atokentype_T { TT_NULL, TT_INVALID, /* symbols */ TT_LPAREN, TT_RPAREN, TT_TILDE, TT_EXCL, TT_PERCENT, TT_PLUS, TT_MINUS, TT_PLUSPLUS, TT_MINUSMINUS, TT_ASTER, TT_SLASH, TT_LESS, TT_GREATER, TT_LESSLESS, TT_GREATERGREATER, TT_LESSEQUAL, TT_GREATEREQUAL, TT_EQUALEQUAL, TT_EXCLEQUAL, TT_AMP, TT_AMPAMP, TT_HAT, TT_PIPE, TT_PIPEPIPE, TT_QUESTION, TT_COLON, TT_EQUAL, TT_PLUSEQUAL, TT_MINUSEQUAL, TT_ASTEREQUAL, TT_SLASHEQUAL, TT_PERCENTEQUAL, TT_LESSLESSEQUAL, TT_GREATERGREATEREQUAL, TT_AMPEQUAL, TT_HATEQUAL, TT_PIPEEQUAL, /* numbers */ TT_NUMBER, /* identifiers */ TT_IDENTIFIER, } atokentype_T; typedef struct atoken_T { atokentype_T type; word_T word; /* valid only for numbers and identifiers */ } atoken_T; typedef struct evalinfo_T { const wchar_t *exp; /* expression to parse and calculate */ size_t index; /* index of next token */ atoken_T atoken; /* current token */ bool parseonly; /* only parse the expression: don't calculate */ bool error; /* true if there is an error */ char *savelocale; /* original LC_NUMERIC locale */ } evalinfo_T; static void evaluate( const wchar_t *exp, value_T *result, evalinfo_T *info, bool coerce) __attribute__((nonnull)); static void parse_assignment(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static bool do_assignment(const word_T *word, const value_T *value) __attribute__((nonnull)); static wchar_t *value_to_string(const value_T *value) __attribute__((nonnull,malloc,warn_unused_result)); static bool do_binary_calculation( evalinfo_T *info, atokentype_T ttype, value_T *lhs, value_T *rhs, value_T *result) __attribute__((nonnull)); static long do_long_calculation1(atokentype_T ttype, long v1, long v2); static long do_long_calculation2(atokentype_T ttype, long v1, long v2); static double do_double_calculation(atokentype_T ttype, double v1, double v2); static long do_double_comparison(atokentype_T ttype, double v1, double v2); static void parse_conditional(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_logical_or(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_logical_and(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_inclusive_or(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_exclusive_or(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_and(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_equality(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_relational(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_shift(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_additive(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_multiplicative(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_prefix(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_postfix(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void do_increment_or_decrement(atokentype_T ttype, value_T *value) __attribute__((nonnull)); static void parse_primary(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void parse_as_number(evalinfo_T *info, value_T *result) __attribute__((nonnull)); static void coerce_number(evalinfo_T *info, value_T *value) __attribute__((nonnull)); static void coerce_integer(evalinfo_T *info, value_T *value) __attribute__((nonnull)); static valuetype_T coerce_type(evalinfo_T *info, value_T *value1, value_T *value2) __attribute__((nonnull)); static void next_token(evalinfo_T *info) __attribute__((nonnull)); static bool fail_if_will_divide_by_zero( atokentype_T op, const value_T *rhs, evalinfo_T *info, value_T *result) __attribute__((nonnull)); /* Evaluates the specified string as an arithmetic expression. * The argument string is freed in this function. * The result is converted into a string and returned as a newly-malloced * string. On error, an error message is printed to the standard error and NULL * is returned. */ wchar_t *evaluate_arithmetic(wchar_t *exp) { value_T result; evalinfo_T info; evaluate(exp, &result, &info, posixly_correct); wchar_t *resultstr; if (info.error) { resultstr = NULL; } else if (info.atoken.type == TT_NULL) { resultstr = value_to_string(&result); } else { if (info.atoken.type != TT_INVALID) xerror(0, Ngt("arithmetic: invalid syntax")); resultstr = NULL; } free(exp); return resultstr; } /* Evaluates the specified string as an arithmetic expression. * The argument string is freed in this function. * The expression must yield a valid integer value, which is assigned to * `*valuep'. Otherwise, an error message is printed. * Returns true iff successful. */ bool evaluate_index(wchar_t *exp, ssize_t *valuep) { value_T result; evalinfo_T info; evaluate(exp, &result, &info, true); bool ok; if (info.error) { ok = false; } else if (info.atoken.type == TT_NULL) { if (result.type == VT_LONG) { #if LONG_MAX > SSIZE_MAX if (result.v_long > (long) SSIZE_MAX) *valuep = SSIZE_MAX; else #endif #if LONG_MIN < -SSIZE_MAX if (result.v_long < (long) -SSIZE_MAX) *valuep = -SSIZE_MAX; else #endif *valuep = (ssize_t) result.v_long; ok = true; } else { xerror(0, Ngt("the index is not an integer")); ok = false; } } else { if (info.atoken.type != TT_INVALID) xerror(0, Ngt("arithmetic: invalid syntax")); ok = false; } free(exp); return ok; } void evaluate( const wchar_t *exp, value_T *result, evalinfo_T *info, bool coerce) { info->exp = exp; info->index = 0; info->parseonly = false; info->error = false; info->savelocale = xstrdup(setlocale(LC_NUMERIC, NULL)); next_token(info); parse_assignment(info, result); if (coerce) coerce_number(info, result); free(info->savelocale); } /* Parses an assignment expression. * AssignmentExp := ConditionalExp * | ConditionalExp AssignmentOperator AssignmentExp */ void parse_assignment(evalinfo_T *info, value_T *result) { parse_conditional(info, result); atokentype_T ttype = info->atoken.type; switch (ttype) { case TT_EQUAL: case TT_PLUSEQUAL: case TT_MINUSEQUAL: case TT_ASTEREQUAL: case TT_SLASHEQUAL: case TT_PERCENTEQUAL: case TT_LESSLESSEQUAL: case TT_GREATERGREATEREQUAL: case TT_AMPEQUAL: case TT_HATEQUAL: case TT_PIPEEQUAL: { value_T rhs; next_token(info); parse_assignment(info, &rhs); if (result->type == VT_VAR) { word_T saveword = result->v_var; if (!do_binary_calculation( info, ttype, result, &rhs, result)) break; if (!do_assignment(&saveword, result)) info->error = true, result->type = VT_INVALID; } else if (result->type != VT_INVALID) { /* TRANSLATORS: This error message is shown when the target * of an assignment is not a variable. */ xerror(0, Ngt("arithmetic: cannot assign to a number")); info->error = true; result->type = VT_INVALID; } break; } default: break; } } /* Assigns the specified `value' to the variable specified by `word'. * Returns false on error. */ bool do_assignment(const word_T *word, const value_T *value) { wchar_t *vstr = value_to_string(value); if (vstr == NULL) return false; wchar_t name[word->length + 1]; wmemcpy(name, word->contents, word->length); name[word->length] = L'\0'; return set_variable(name, vstr, SCOPE_GLOBAL, false); } /* Converts `value' to a newly-malloced wide string. * Returns NULL on error. */ wchar_t *value_to_string(const value_T *value) { switch (value->type) { case VT_INVALID: return NULL; case VT_LONG: return malloc_wprintf(L"%ld", value->v_long); case VT_DOUBLE: return malloc_wprintf(L"%.*g", DBL_DIG, value->v_double); case VT_VAR: { wchar_t name[value->v_var.length + 1]; wmemcpy(name, value->v_var.contents, value->v_var.length); name[value->v_var.length] = L'\0'; const wchar_t *var = getvar(name); if (var != NULL) return xwcsdup(var); if (shopt_unset) return malloc_wprintf(L"%ld", 0L); xerror(0, Ngt("arithmetic: parameter `%ls' is not set"), name); return NULL; } } assert(false); } /* Applies the binary operation defined by token `ttype' to operands `lhs' and * `rhs'. The operands may be modified as a result of coercion. The result is * assigned to `*result' unless there is an error. * Returns true iff successful. */ bool do_binary_calculation( evalinfo_T *info, atokentype_T ttype, value_T *lhs, value_T *rhs, value_T *result) { switch (ttype) { case TT_ASTER: case TT_ASTEREQUAL: case TT_SLASH: case TT_SLASHEQUAL: case TT_PERCENT: case TT_PERCENTEQUAL: case TT_PLUS: case TT_PLUSEQUAL: case TT_MINUS: case TT_MINUSEQUAL: result->type = coerce_type(info, lhs, rhs); if (fail_if_will_divide_by_zero(ttype, rhs, info, result)) return false; switch (result->type) { case VT_LONG: result->v_long = do_long_calculation1( ttype, lhs->v_long, rhs->v_long); break; case VT_DOUBLE: result->v_double = do_double_calculation( ttype, lhs->v_double, rhs->v_double); break; case VT_VAR: assert(false); case VT_INVALID: break; } break; case TT_LESSLESS: case TT_LESSLESSEQUAL: case TT_GREATERGREATER: case TT_GREATERGREATEREQUAL: case TT_AMP: case TT_AMPEQUAL: case TT_HAT: case TT_HATEQUAL: case TT_PIPE: case TT_PIPEEQUAL: coerce_integer(info, lhs); coerce_integer(info, rhs); if (lhs->type == VT_LONG && rhs->type == VT_LONG) { result->type = VT_LONG; result->v_long = do_long_calculation2(ttype, lhs->v_long, rhs->v_long); } else { result->type = VT_INVALID; } break; case TT_EQUAL: *result = *rhs; break; default: assert(false); } return true; } /* Does unary or binary long calculation according to the specified operator * token. Division by zero is not allowed. */ long do_long_calculation1(atokentype_T ttype, long v1, long v2) { switch (ttype) { case TT_PLUS: case TT_PLUSEQUAL: return v1 + v2; case TT_MINUS: case TT_MINUSEQUAL: return v1 - v2; case TT_ASTER: case TT_ASTEREQUAL: return v1 * v2; case TT_SLASH: case TT_SLASHEQUAL: return v1 / v2; case TT_PERCENT: case TT_PERCENTEQUAL: return v1 % v2; default: assert(false); } } /* Does unary or binary long calculation according to the specified operator * token. */ long do_long_calculation2(atokentype_T ttype, long v1, long v2) { switch (ttype) { case TT_LESSLESS: case TT_LESSLESSEQUAL: return v1 << v2; case TT_GREATERGREATER: case TT_GREATERGREATEREQUAL: return v1 >> v2; case TT_LESS: return v1 < v2; case TT_LESSEQUAL: return v1 <= v2; case TT_GREATER: return v1 > v2; case TT_GREATEREQUAL: return v1 >= v2; case TT_EQUALEQUAL: return v1 == v2; case TT_EXCLEQUAL: return v1 != v2; case TT_AMP: case TT_AMPEQUAL: return v1 & v2; case TT_HAT: case TT_HATEQUAL: return v1 ^ v2; case TT_PIPE: case TT_PIPEEQUAL: return v1 | v2; default: assert(false); } } /* Does unary or binary double calculation according to the specified operator * token. */ double do_double_calculation(atokentype_T ttype, double v1, double v2) { switch (ttype) { case TT_PLUS: case TT_PLUSEQUAL: return v1 + v2; case TT_MINUS: case TT_MINUSEQUAL: return v1 - v2; case TT_ASTER: case TT_ASTEREQUAL: return v1 * v2; case TT_SLASH: case TT_SLASHEQUAL: return v1 / v2; case TT_PERCENT: case TT_PERCENTEQUAL: return fmod(v1, v2); default: assert(false); } } /* Does double comparison according to the specified operator token. */ long do_double_comparison(atokentype_T ttype, double v1, double v2) { switch (ttype) { case TT_LESS: return v1 < v2; case TT_LESSEQUAL: return v1 <= v2; case TT_GREATER: return v1 > v2; case TT_GREATEREQUAL: return v1 >= v2; case TT_EQUALEQUAL: return v1 == v2; case TT_EXCLEQUAL: return v1 != v2; default: assert(false); } } /* Parses a conditional expression. * ConditionalExp := LogicalOrExp * | LogicalOrExp "?" AssignmentExp ":" ConditionalExp */ void parse_conditional(evalinfo_T *info, value_T *result) { bool saveparseonly = info->parseonly; for (;;) { value_T dummy; value_T *result2; result2 = info->parseonly ? &dummy : result; parse_logical_or(info, result2); if (info->atoken.type != TT_QUESTION) break; bool cond, valid = true; coerce_number(info, result2); next_token(info); switch (result2->type) { case VT_INVALID: valid = false, cond = true; break; case VT_LONG: cond = result2->v_long; break; case VT_DOUBLE: cond = result2->v_double; break; default: assert(false); } bool saveparseonly2 = info->parseonly || !valid; info->parseonly = saveparseonly2 || !cond; result2 = info->parseonly ? &dummy : result; parse_assignment(info, result2); if (info->atoken.type != TT_COLON) { xerror(0, Ngt("arithmetic: `%ls' is missing"), L":"); info->error = true; result->type = VT_INVALID; break; } next_token(info); info->parseonly = saveparseonly2 || cond; } info->parseonly = saveparseonly; if (info->parseonly) result->type = VT_INVALID; } /* Parses a logical OR expression. * LogicalOrExp := LogicalAndExp | LogicalOrExp "||" LogicalAndExp */ void parse_logical_or(evalinfo_T *info, value_T *result) { bool saveparseonly = info->parseonly; parse_logical_and(info, result); while (info->atoken.type == TT_PIPEPIPE) { bool value, valid = true; coerce_number(info, result); next_token(info); switch (result->type) { case VT_INVALID: valid = false, value = true; break; case VT_LONG: value = result->v_long; break; case VT_DOUBLE: value = result->v_double; break; default: assert(false); } info->parseonly |= value; parse_logical_and(info, result); coerce_number(info, result); if (!value) switch (result->type) { case VT_INVALID: valid = false; break; case VT_LONG: value = result->v_long; break; case VT_DOUBLE: value = result->v_double; break; default: assert(false); } if (valid) result->type = VT_LONG, result->v_long = value; else result->type = VT_INVALID; } info->parseonly = saveparseonly; } /* Parses a logical AND expression. * LogicalAndExp := InclusiveOrExp | LogicalAndExp "&&" InclusiveOrExp */ void parse_logical_and(evalinfo_T *info, value_T *result) { bool saveparseonly = info->parseonly; parse_inclusive_or(info, result); while (info->atoken.type == TT_AMPAMP) { bool value, valid = true; coerce_number(info, result); next_token(info); switch (result->type) { case VT_INVALID: valid = false, value = false; break; case VT_LONG: value = result->v_long; break; case VT_DOUBLE: value = result->v_double; break; default: assert(false); } info->parseonly |= !value; parse_inclusive_or(info, result); coerce_number(info, result); if (value) switch (result->type) { case VT_INVALID: valid = false; break; case VT_LONG: value = result->v_long; break; case VT_DOUBLE: value = result->v_double; break; default: assert(false); } if (valid) result->type = VT_LONG, result->v_long = value; else result->type = VT_INVALID; } info->parseonly = saveparseonly; } /* Parses an inclusive OR expression. * InclusiveOrExp := ExclusiveOrExp | InclusiveOrExp "|" ExclusiveOrExp */ void parse_inclusive_or(evalinfo_T *info, value_T *result) { parse_exclusive_or(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_PIPE: next_token(info); parse_exclusive_or(info, &rhs); do_binary_calculation(info, TT_PIPE, result, &rhs, result); break; default: return; } } } /* Parses an exclusive OR expression. * ExclusiveOrExp := AndExp | ExclusiveOrExp "^" AndExp */ void parse_exclusive_or(evalinfo_T *info, value_T *result) { parse_and(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_HAT: next_token(info); parse_and(info, &rhs); do_binary_calculation(info, TT_HAT, result, &rhs, result); break; default: return; } } } /* Parses an AND expression. * AndExp := EqualityExp | AndExp "&" EqualityExp */ void parse_and(evalinfo_T *info, value_T *result) { parse_equality(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_AMP: next_token(info); parse_equality(info, &rhs); do_binary_calculation(info, TT_AMP, result, &rhs, result); break; default: return; } } } /* Parses an equality expression. * EqualityExp := RelationalExp * | EqualityExp "==" RelationalExp * | EqualityExp "!=" RelationalExp */ void parse_equality(evalinfo_T *info, value_T *result) { parse_relational(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_EQUALEQUAL: case TT_EXCLEQUAL: next_token(info); parse_relational(info, &rhs); switch (coerce_type(info, result, &rhs)) { case VT_LONG: result->v_long = do_long_calculation2(ttype, result->v_long, rhs.v_long); break; case VT_DOUBLE: result->v_long = do_double_comparison(ttype, result->v_double, rhs.v_double); result->type = VT_LONG; break; case VT_INVALID: result->type = VT_INVALID; break; case VT_VAR: assert(false); } break; default: return; } } } /* Parses a relational expression. * RelationalExp := ShiftExp * | RelationalExp "<" ShiftExp * | RelationalExp ">" ShiftExp * | RelationalExp "<=" ShiftExp * | RelationalExp ">=" ShiftExp */ void parse_relational(evalinfo_T *info, value_T *result) { parse_shift(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_LESS: case TT_LESSEQUAL: case TT_GREATER: case TT_GREATEREQUAL: next_token(info); parse_shift(info, &rhs); switch (coerce_type(info, result, &rhs)) { case VT_LONG: result->v_long = do_long_calculation2(ttype, result->v_long, rhs.v_long); break; case VT_DOUBLE: result->v_long = do_double_comparison(ttype, result->v_double, rhs.v_double); result->type = VT_LONG; break; case VT_INVALID: result->type = VT_INVALID; break; case VT_VAR: assert(false); } break; default: return; } } } /* Parses a shift expression. * ShiftExp := AdditiveExp * | ShiftExp "<<" AdditiveExp | ShiftExp ">>" AdditiveExp */ void parse_shift(evalinfo_T *info, value_T *result) { parse_additive(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_LESSLESS: case TT_GREATERGREATER: next_token(info); parse_additive(info, &rhs); do_binary_calculation(info, ttype, result, &rhs, result); break; default: return; } } } /* Parses an additive expression. * AdditiveExp := MultiplicativeExp * | AdditiveExp "+" MultiplicativeExp * | AdditiveExp "-" MultiplicativeExp */ void parse_additive(evalinfo_T *info, value_T *result) { parse_multiplicative(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_PLUS: case TT_MINUS: next_token(info); parse_multiplicative(info, &rhs); do_binary_calculation(info, ttype, result, &rhs, result); break; default: return; } } } /* Parses a multiplicative expression. * MultiplicativeExp := PrefixExp * | MultiplicativeExp "*" PrefixExp * | MultiplicativeExp "/" PrefixExp * | MultiplicativeExp "%" PrefixExp */ void parse_multiplicative(evalinfo_T *info, value_T *result) { parse_prefix(info, result); for (;;) { atokentype_T ttype = info->atoken.type; value_T rhs; switch (ttype) { case TT_ASTER: case TT_SLASH: case TT_PERCENT: next_token(info); parse_prefix(info, &rhs); do_binary_calculation(info, ttype, result, &rhs, result); break; default: return; } } } /* Parses a prefix expression. * PrefixExp := PostfixExp * | "++" PrefixExp | "--" PrefixExp * | "+" PrefixExp | "-" PrefixExp * | "~" PrefixExp | "!" PrefixExp */ void parse_prefix(evalinfo_T *info, value_T *result) { atokentype_T ttype = info->atoken.type; switch (ttype) { case TT_PLUSPLUS: case TT_MINUSMINUS: next_token(info); parse_prefix(info, result); if (posixly_correct) { xerror(0, Ngt("arithmetic: operator `%ls' is not supported"), (ttype == TT_PLUSPLUS) ? L"++" : L"--"); info->error = true; result->type = VT_INVALID; } else if (result->type == VT_VAR) { word_T saveword = result->v_var; coerce_number(info, result); do_increment_or_decrement(ttype, result); if (!do_assignment(&saveword, result)) info->error = true, result->type = VT_INVALID; } else if (result->type != VT_INVALID) { /* TRANSLATORS: This error message is shown when the operand of * the "++" or "--" operator is not a variable. */ xerror(0, Ngt("arithmetic: operator `%ls' requires a variable"), (info->atoken.type == TT_PLUSPLUS) ? L"++" : L"--"); info->error = true; result->type = VT_INVALID; } break; case TT_PLUS: case TT_MINUS: next_token(info); parse_prefix(info, result); coerce_number(info, result); if (ttype == TT_MINUS) { switch (result->type) { case VT_LONG: result->v_long = -result->v_long; break; case VT_DOUBLE: result->v_double = -result->v_double; break; case VT_INVALID: break; default: assert(false); } } break; case TT_TILDE: next_token(info); parse_prefix(info, result); coerce_integer(info, result); if (result->type == VT_LONG) result->v_long = ~result->v_long; break; case TT_EXCL: next_token(info); parse_prefix(info, result); coerce_number(info, result); switch (result->type) { case VT_LONG: result->v_long = !result->v_long; break; case VT_DOUBLE: result->type = VT_LONG; result->v_long = !result->v_double; break; case VT_INVALID: break; default: assert(false); } break; default: parse_postfix(info, result); break; } } /* Parses a postfix expression. * PostfixExp := PrimaryExp * | PostfixExp "++" | PostfixExp "--" */ void parse_postfix(evalinfo_T *info, value_T *result) { parse_primary(info, result); for (;;) { switch (info->atoken.type) { case TT_PLUSPLUS: case TT_MINUSMINUS: if (posixly_correct) { xerror(0, Ngt("arithmetic: operator `%ls' is not supported"), (info->atoken.type == TT_PLUSPLUS) ? L"++" : L"--"); info->error = true; result->type = VT_INVALID; } else if (result->type == VT_VAR) { word_T saveword = result->v_var; coerce_number(info, result); value_T value = *result; do_increment_or_decrement(info->atoken.type, &value); if (!do_assignment(&saveword, &value)) { info->error = true; result->type = VT_INVALID; } } else if (result->type != VT_INVALID) { xerror(0, Ngt("arithmetic: " "operator `%ls' requires a variable"), (info->atoken.type == TT_PLUSPLUS) ? L"++" : L"--"); info->error = true; result->type = VT_INVALID; } next_token(info); break; default: return; } } } /* Increment or decrement the specified value. * `ttype' must be either TT_PLUSPLUS or TT_MINUSMINUS and the `value' must be * `coerce_number'ed. */ void do_increment_or_decrement(atokentype_T ttype, value_T *value) { if (ttype == TT_PLUSPLUS) { switch (value->type) { case VT_LONG: value->v_long++; break; case VT_DOUBLE: value->v_double++; break; case VT_INVALID: break; default: assert(false); } } else { switch (value->type) { case VT_LONG: value->v_long--; break; case VT_DOUBLE: value->v_double--; break; case VT_INVALID: break; default: assert(false); } } } /* Parses a primary expression. * PrimaryExp := "(" AssignmentExp ")" | Number | Identifier */ void parse_primary(evalinfo_T *info, value_T *result) { switch (info->atoken.type) { case TT_LPAREN: next_token(info); parse_assignment(info, result); if (info->atoken.type == TT_RPAREN) { next_token(info); } else { xerror(0, Ngt("arithmetic: `%ls' is missing"), L")"); info->error = true; result->type = VT_INVALID; } break; case TT_NUMBER: parse_as_number(info, result); next_token(info); break; case TT_IDENTIFIER: result->type = VT_VAR; result->v_var = info->atoken.word; next_token(info); break; default: xerror(0, Ngt("arithmetic: a value is missing")); info->error = true; result->type = VT_INVALID; break; } if (info->parseonly) result->type = VT_INVALID; } /* Parses the current word as a number literal. */ void parse_as_number(evalinfo_T *info, value_T *result) { word_T *word = &info->atoken.word; wchar_t wordstr[word->length + 1]; wcsncpy(wordstr, word->contents, word->length); wordstr[word->length] = L'\0'; long longresult; if (xwcstol(wordstr, 0, &longresult)) { result->type = VT_LONG; result->v_long = longresult; return; } if (!posixly_correct) { double doubleresult; wchar_t *end; setlocale(LC_NUMERIC, "C"); errno = 0; doubleresult = wcstod(wordstr, &end); bool ok = (errno == 0 && *end == L'\0'); setlocale(LC_NUMERIC, info->savelocale); if (ok) { result->type = VT_DOUBLE; result->v_double = doubleresult; return; } } xerror(0, Ngt("arithmetic: `%ls' is not a valid number"), wordstr); info->error = true; result->type = VT_INVALID; } /* If the value is of the VT_VAR type, change it into VT_LONG/VT_DOUBLE. * If the variable specified by the value is unset, it is assumed 0. * On parse error, false is returned and the value is unspecified. */ void coerce_number(evalinfo_T *info, value_T *value) { if (value->type != VT_VAR) return; const wchar_t *varvalue; { word_T *name = &value->v_var; wchar_t namestr[name->length + 1]; wmemcpy(namestr, name->contents, name->length); namestr[name->length] = L'\0'; varvalue = getvar(namestr); if (varvalue == NULL && !shopt_unset) { xerror(0, Ngt("arithmetic: parameter `%ls' is not set"), namestr); info->error = true; value->type = VT_INVALID; return; } } if (varvalue == NULL || varvalue[0] == L'\0') { value->type = VT_LONG; value->v_long = 0; return; } long longresult; if (xwcstol(varvalue, 0, &longresult)) { value->type = VT_LONG; value->v_long = longresult; return; } if (!posixly_correct) { double doubleresult; wchar_t *end; errno = 0; doubleresult = wcstod(varvalue, &end); if (errno == 0 && *end == L'\0') { value->type = VT_DOUBLE; value->v_double = doubleresult; return; } } xerror(0, Ngt("arithmetic: `%ls' is not a valid number"), varvalue); info->error = true; value->type = VT_INVALID; return; } /* Does `coerce_number' and if the result is of VT_DOUBLE, converts into * VT_LONG. */ void coerce_integer(evalinfo_T *info, value_T *value) { coerce_number(info, value); if (value->type == VT_DOUBLE) { value->type = VT_LONG; value->v_long = (long) value->v_double; } } /* Unifies the types of the numeric values. * First, the given values are `coerce_number'ed. * Then, if one of the values is of VT_LONG and the other is of VT_DOUBLE, the * VT_LONG value is converted into VT_DOUBLE. * If the both values are of VT_LONG, VT_LONG is returned without conversion. * If either value is of VT_DOUBLE and the other is not VT_INVALID, VT_DOUBLE * is returned after conversion. If either value is VT_INVALID, the return * value is VT_INVALID. This function never returns VT_VAR. */ valuetype_T coerce_type(evalinfo_T *info, value_T *value1, value_T *value2) { coerce_number(info, value1); coerce_number(info, value2); if (value1->type == value2->type) return value1->type; if (value1->type == VT_INVALID || value2->type == VT_INVALID) return VT_INVALID; assert(value1->type == VT_LONG || value1->type == VT_DOUBLE); assert(value2->type == VT_LONG || value2->type == VT_DOUBLE); value_T *value_to_coerce = (value1->type == VT_LONG) ? value1 : value2; value_to_coerce->type = VT_DOUBLE; value_to_coerce->v_double = (double) value_to_coerce->v_long; return VT_DOUBLE; } /* Moves to the next token. * The contents of `*info' is updated. * If there is no more token, `info->index' indicates the terminating null char * and a TT_NULL token is returned. On error, TT_INVALID is returned. */ void next_token(evalinfo_T *info) { /* skip spaces */ while (iswspace(info->exp[info->index])) info->index++; wchar_t c = info->exp[info->index]; switch (c) { case L'\0': info->atoken.type = TT_NULL; return; case L'(': info->atoken.type = TT_LPAREN; info->index++; break; case L')': info->atoken.type = TT_RPAREN; info->index++; break; case L'~': info->atoken.type = TT_TILDE; info->index++; break; case L'!': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_EXCLEQUAL; info->index += 2; break; default: info->atoken.type = TT_EXCL; info->index += 1; break; } break; case L'%': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_PERCENTEQUAL; info->index += 2; break; default: info->atoken.type = TT_PERCENT; info->index += 1; break; } break; case L'+': switch (info->exp[info->index + 1]) { case L'+': info->atoken.type = TT_PLUSPLUS; info->index += 2; break; case L'=': info->atoken.type = TT_PLUSEQUAL; info->index += 2; break; default: info->atoken.type = TT_PLUS; info->index += 1; break; } break; case L'-': switch (info->exp[info->index + 1]) { case L'-': info->atoken.type = TT_MINUSMINUS; info->index += 2; break; case L'=': info->atoken.type = TT_MINUSEQUAL; info->index += 2; break; default: info->atoken.type = TT_MINUS; info->index += 1; break; } break; case L'*': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_ASTEREQUAL; info->index += 2; break; default: info->atoken.type = TT_ASTER; info->index += 1; break; } break; case L'/': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_SLASHEQUAL; info->index += 2; break; default: info->atoken.type = TT_SLASH; info->index += 1; break; } break; case L'<': { info->index++; bool twin = info->exp[info->index] == L'<'; if (twin) info->index++; bool equal = info->exp[info->index] == L'='; if (equal) info->index++; info->atoken.type = twin ? equal ? TT_LESSLESSEQUAL : TT_LESSLESS : equal ? TT_LESSEQUAL : TT_LESS; } break; case L'>': { info->index++; bool twin = info->exp[info->index] == L'>'; if (twin) info->index++; bool equal = info->exp[info->index] == L'='; if (equal) info->index++; info->atoken.type = twin ? equal ? TT_GREATERGREATEREQUAL : TT_GREATERGREATER : equal ? TT_GREATEREQUAL : TT_GREATER; } break; case L'=': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_EQUALEQUAL; info->index += 2; break; default: info->atoken.type = TT_EQUAL; info->index += 1; break; } break; case L'&': switch (info->exp[info->index + 1]) { case L'&': info->atoken.type = TT_AMPAMP; info->index += 2; break; case L'=': info->atoken.type = TT_AMPEQUAL; info->index += 2; break; default: info->atoken.type = TT_AMP; info->index += 1; break; } break; case L'|': switch (info->exp[info->index + 1]) { case L'|': info->atoken.type = TT_PIPEPIPE; info->index += 2; break; case L'=': info->atoken.type = TT_PIPEEQUAL; info->index += 2; break; default: info->atoken.type = TT_PIPE; info->index += 1; break; } break; case L'^': switch (info->exp[info->index + 1]) { case L'=': info->atoken.type = TT_HATEQUAL; info->index += 2; break; default: info->atoken.type = TT_HAT; info->index += 1; break; } break; case L'?': info->atoken.type = TT_QUESTION; info->index++; break; case L':': info->atoken.type = TT_COLON; info->index++; break; case L'_': goto parse_identifier; default: assert(!iswspace(c)); if (c == L'.' || iswdigit(c)) { /* number */ size_t startindex = info->index; parse_num: do c = info->exp[++info->index]; while (c == L'.' || iswalnum(c)); if (c == L'+' || c == L'-') { c = info->exp[info->index - 1]; if (c == L'E' || c == L'e') goto parse_num; } info->atoken.type = TT_NUMBER; info->atoken.word.contents = &info->exp[startindex]; info->atoken.word.length = info->index - startindex; } else if (iswalpha(c)) { parse_identifier:; size_t startindex = info->index; do c = info->exp[++info->index]; while (c == L'_' || iswalnum(c)); info->atoken.type = TT_IDENTIFIER; info->atoken.word.contents = &info->exp[startindex]; info->atoken.word.length = info->index - startindex; } else { xerror(0, Ngt("arithmetic: `%lc' is not " "a valid number or operator"), (wint_t) c); info->error = true; info->atoken.type = TT_INVALID; } break; } } /* If `op' is a division operator and `rhs' is zero, then prints an error * message, sets `info->error' to true, sets `result->type' to VT_INVALID, and * returns true. Otherwise, just returns false. * `rhs->type' must not be VT_VAR. */ bool fail_if_will_divide_by_zero( atokentype_T op, const value_T *rhs, evalinfo_T *info, value_T *result) { switch (op) { case TT_SLASH: case TT_SLASHEQUAL: case TT_PERCENT: case TT_PERCENTEQUAL: switch (rhs->type) { case VT_LONG: if (rhs->v_long == 0) goto fail; break; case VT_DOUBLE: #if DOUBLE_DIVISION_BY_ZERO_ERROR if (rhs->v_double == 0.0) goto fail; #endif break; case VT_VAR: assert(false); case VT_INVALID: break; } /* falls through */ default: return false; } fail: xerror(0, Ngt("arithmetic: division by zero")); info->error = true; result->type = VT_INVALID; return true; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/arith.h000066400000000000000000000021221354143602500136740ustar00rootroot00000000000000/* Yash: yet another shell */ /* arith.h: arithmetic expansion */ /* (C) 2007-2009 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_ARITH_H #define YASH_ARITH_H #include #include extern wchar_t *evaluate_arithmetic(wchar_t *exp) __attribute__((nonnull,malloc,warn_unused_result)); extern _Bool evaluate_index(wchar_t *exp, ssize_t *valuep) __attribute__((nonnull)); #endif /* YASH_ARITH_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtin.c000066400000000000000000000450161354143602500142370ustar00rootroot00000000000000/* Yash: yet another shell */ /* builtin.c: built-in commands */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "builtin.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include "alias.h" #include "exec.h" #include "hashtable.h" #if YASH_ENABLE_HISTORY # include "history.h" #endif #include "job.h" #include "option.h" #include "path.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "xfnmatch.h" #include "yash.h" #if YASH_ENABLE_PRINTF # include "builtins/printf.h" #endif #if YASH_ENABLE_TEST # include "builtins/test.h" #endif #if YASH_ENABLE_ULIMIT # include "builtins/ulimit.h" #endif #if YASH_ENABLE_LINEEDIT # include "lineedit/complete.h" # include "lineedit/keymap.h" #endif /* Rules about built-in commands: * - `argc' passed to a built-in is at least one; The command name is given in * `argv[0]'. * - `argv' may be rearranged and the values of the argument strings may be * changed in the built-in. However, the argument strings may not be `free'd * or `realloc'ed. * - Built-ins may sleep or wait, but cannot be stopped. */ /* A hashtable from names of built-ins (const char *) to built-in info * structures (const builtin_T *). */ static hashtable_T builtins; /* Initializes `builtins' */ void init_builtin(void) { assert(builtins.capacity == 0); ht_initwithcapacity(&builtins, hashstr, htstrcmp, 53); #if YASH_ENABLE_HELP # define DEFBUILTIN(name,func,type,help,syntax,options) \ do { \ static const builtin_T bi = { func, type, help, syntax, options, }; \ ht_set(&builtins, name, &bi); \ } while (0) #else # define DEFBUILTIN(name,func,type,help,syntax,options) \ do { \ static const builtin_T bi = { func, type, }; \ ht_set(&builtins, name, &bi); \ } while (0) #endif /* defined in "builtin.c" */ DEFBUILTIN(":", true_builtin, BI_SPECIAL, colon_help, colon_syntax, NULL); DEFBUILTIN("true", true_builtin, BI_SEMISPECIAL, true_help, true_syntax, NULL); DEFBUILTIN("false", false_builtin, BI_SEMISPECIAL, false_help, false_syntax, NULL); #if YASH_ENABLE_HELP DEFBUILTIN("help", help_builtin, BI_SEMISPECIAL, help_help, help_syntax, help_option); #endif /* defined in "option.c" */ DEFBUILTIN("set", set_builtin, BI_SPECIAL, set_help, set_syntax, NULL); /* defined in "path.c" */ DEFBUILTIN("cd", cd_builtin, BI_SEMISPECIAL, cd_help, cd_syntax, cd_options); DEFBUILTIN("pwd", pwd_builtin, BI_SEMISPECIAL, pwd_help, pwd_syntax, pwd_options); DEFBUILTIN("hash", hash_builtin, BI_SEMISPECIAL, hash_help, hash_syntax, hash_options); DEFBUILTIN("umask", umask_builtin, BI_SEMISPECIAL, umask_help, umask_syntax, umask_options); /* defined in "alias.c" */ DEFBUILTIN("alias", alias_builtin, BI_SEMISPECIAL, alias_help, alias_syntax, alias_options); DEFBUILTIN("unalias", unalias_builtin, BI_SEMISPECIAL, unalias_help, unalias_syntax, all_help_options); /* defined in "variable.c" */ DEFBUILTIN("typeset", typeset_builtin, BI_SEMISPECIAL, typeset_help, typeset_syntax, typeset_options); DEFBUILTIN("export", typeset_builtin, BI_SPECIAL, export_help, export_syntax, typeset_options); DEFBUILTIN("local", typeset_builtin, BI_SEMISPECIAL, local_help, local_syntax, local_options); DEFBUILTIN("readonly", typeset_builtin, BI_SPECIAL, readonly_help, readonly_syntax, typeset_options); #if YASH_ENABLE_ARRAY DEFBUILTIN("array", array_builtin, BI_REGULAR, array_help, array_syntax, array_options); #endif DEFBUILTIN("unset", unset_builtin, BI_SPECIAL, unset_help, unset_syntax, unset_options); DEFBUILTIN("shift", shift_builtin, BI_SPECIAL, shift_help, shift_syntax, shift_options); DEFBUILTIN("getopts", getopts_builtin, BI_SEMISPECIAL, getopts_help, getopts_syntax, help_option); DEFBUILTIN("read", read_builtin, BI_SEMISPECIAL, read_help, read_syntax, read_options); #if YASH_ENABLE_DIRSTACK DEFBUILTIN("pushd", pushd_builtin, BI_SEMISPECIAL, pushd_help, pushd_syntax, pushd_options); DEFBUILTIN("popd", popd_builtin, BI_SEMISPECIAL, popd_help, popd_syntax, help_option); DEFBUILTIN("dirs", dirs_builtin, BI_SEMISPECIAL, dirs_help, dirs_syntax, dirs_options); #endif /* defined in "sig.c" */ DEFBUILTIN("trap", trap_builtin, BI_SPECIAL, trap_help, trap_syntax, trap_options); DEFBUILTIN("kill", kill_builtin, BI_SEMISPECIAL, kill_help, kill_syntax, NULL); /* defined in "job.c" */ DEFBUILTIN("jobs", jobs_builtin, BI_SEMISPECIAL, jobs_help, jobs_syntax, jobs_options); DEFBUILTIN("fg", fg_builtin, BI_SEMISPECIAL, fg_help, fg_syntax, help_option); DEFBUILTIN("bg", fg_builtin, BI_SEMISPECIAL, bg_help, bg_syntax, help_option); DEFBUILTIN("wait", wait_builtin, BI_SEMISPECIAL, wait_help, wait_syntax, help_option); DEFBUILTIN("disown", disown_builtin, BI_SEMISPECIAL, disown_help, disown_syntax, all_help_options); /* defined in "history.c" */ #if YASH_ENABLE_HISTORY DEFBUILTIN("fc", fc_builtin, BI_SEMISPECIAL, fc_help, fc_syntax, fc_options); DEFBUILTIN("history", history_builtin, BI_SEMISPECIAL, history_help, history_syntax, history_options); #endif /* defined in "exec.c" */ DEFBUILTIN("return", return_builtin, BI_SPECIAL, return_help, return_syntax, return_options); DEFBUILTIN("break", break_builtin, BI_SPECIAL, break_help, break_syntax, iter_options); DEFBUILTIN("continue", break_builtin, BI_SPECIAL, continue_help, continue_syntax, iter_options); DEFBUILTIN("eval", eval_builtin, BI_SPECIAL, eval_help, eval_syntax, iter_options); DEFBUILTIN(".", dot_builtin, BI_SPECIAL, dot_help, dot_syntax, dot_options); DEFBUILTIN("exec", exec_builtin, BI_SPECIAL, exec_help, exec_syntax, exec_options); DEFBUILTIN("command", command_builtin, BI_SEMISPECIAL, command_help, command_syntax, command_options); DEFBUILTIN("type", command_builtin, BI_SEMISPECIAL, type_help, type_syntax, command_options); DEFBUILTIN("times", times_builtin, BI_SPECIAL, times_help, times_syntax, help_option); /* defined in "yash.c" */ DEFBUILTIN("exit", exit_builtin, BI_SPECIAL, exit_help, exit_syntax, force_help_options); DEFBUILTIN("suspend", suspend_builtin, BI_SEMISPECIAL, suspend_help, suspend_syntax, force_help_options); /* defined in "builtins/ulimit.c" */ #if YASH_ENABLE_ULIMIT DEFBUILTIN("ulimit", ulimit_builtin, BI_SEMISPECIAL, ulimit_help, ulimit_syntax, ulimit_options); #endif /* defined in "builtins/printf.c" */ #if YASH_ENABLE_PRINTF DEFBUILTIN("echo", echo_builtin, BI_REGULAR, echo_help, echo_syntax, NULL); DEFBUILTIN("printf", printf_builtin, BI_REGULAR, printf_help, printf_syntax, help_option); #endif /* defined in "builtins/test.c" */ #if YASH_ENABLE_TEST DEFBUILTIN("test", test_builtin, BI_REGULAR, test_help, test_syntax, NULL); DEFBUILTIN("[", test_builtin, BI_REGULAR, test_help, test_syntax, NULL); #endif /* defined in "lineedit/complete.c" */ #if YASH_ENABLE_LINEEDIT DEFBUILTIN("complete", complete_builtin, BI_SEMISPECIAL, complete_help, complete_syntax, complete_options); #endif /* defined in "lineedit/keymap.c" */ #if YASH_ENABLE_LINEEDIT DEFBUILTIN("bindkey", bindkey_builtin, BI_SEMISPECIAL, bindkey_help, bindkey_syntax, bindkey_options); #endif #undef DEFBUILTIN } /* Returns the built-in command of the specified name or NULL if not found. */ const builtin_T *get_builtin(const char *name) { return ht_get(&builtins, name).value; } /* Prints the following error message and returns Exit_ERROR: * "the -X option cannot be used with the -Y option", * where X and Y are `opt1' and `opt2', respectively. */ int mutually_exclusive_option_error(wchar_t opt1, wchar_t opt2) { xerror(0, Ngt("the -%lc option cannot be used with the -%lc option"), (wint_t) opt1, (wint_t) opt2); return Exit_ERROR; } /* Checks if the number of operands is in an acceptable range. * `max' must not be less than `min'. * If `min <= count <= max', returns true. * Otherwise, prints an error message and returns false. */ bool validate_operand_count(size_t count, size_t min, size_t max) { assert(min <= max); if (count < min) { insufficient_operands_error(min); return false; } else if (count > max) { too_many_operands_error(max); return false; } return true; } /* Prints the "this command requires an operand" error message and returns * Exit_ERROR. */ int insufficient_operands_error(size_t min_required_operand_count) { xerror(0, ngt("this command requires an operand", "this command requires %zu operands", min_required_operand_count), min_required_operand_count); return Exit_ERROR; } /* Prints the "too many operands" error message and returns Exit_ERROR. */ int too_many_operands_error(size_t max_accepted_operand_count) { if (max_accepted_operand_count == 0) /* TRANSLATORS: This message is printed when a command that takes no * operand was invoked with some operands. */ xerror(0, Ngt("no operand is expected")); else /* TRANSLATORS: This message is printed when a command was invoked with * the wrong number of operands. */ xerror(0, Ngt("too many operands are specified")); return Exit_ERROR; } /* This function is called when an error occurred while executing a special * built-in. If `posixly_correct' and `special_builtin_executed' are true and * `is_interactive_now' is false, `exit_shell_with_status' is called with * `exitstatus'. Otherwise, this function just returns `exitstatus'. */ /* Even though this function is called only while executing a special built-in, * checking `special_builtin_executed' is necessary because * `exit_shell_with_status' should not be called if the special built-in is * being executed indirectly by a non-special built-in. */ int special_builtin_error(int exitstatus) { if (posixly_correct && special_builtin_executed && !is_interactive_now) exit_shell_with_status(exitstatus); return exitstatus; } #if YASH_ENABLE_HELP static int print_builtin_helps(void *const *builtin_names) __attribute__((nonnull)); static int print_builtin_help_body(const wchar_t *name) __attribute__((nonnull)); static bool print_builtin_options(const struct xgetopt_T *options); static bool there_is_any_short_option(const struct xgetopt_T *options) __attribute__((nonnull,pure)); static void format_option_list_entry( const struct xgetopt_T *restrict opt, xstrbuf_T *restrict buf, bool print_short_option) __attribute__((nonnull)); /* Prints description of the specified built-in to the standard output. * Returns Exit_SUCCESS if the built-in was found and the help was printed. * Returns Exit_FAILURE if the built-in was not found or an error occurred. */ int print_builtin_help(const wchar_t *name) { return print_builtin_helps((void *[]) { (void *) name, NULL, }); } /* Prints description of built-ins to the standard output. * `builtin_names' must point to a NULL-terminated array of pointers to wide * strings specifying the names of built-ins. * Returns Exit_SUCCESS if the built-in was found and the help was printed. * Returns Exit_FAILURE if the built-in was not found or an error occurred. */ int print_builtin_helps(void *const *builtin_names) { for (; *builtin_names != NULL; builtin_names++) if (print_builtin_help_body(*builtin_names) != Exit_SUCCESS) return Exit_FAILURE; if (!xprintf(gt("Try `man yash' for details.\n"))) return Exit_FAILURE; return Exit_SUCCESS; } /* Prints description of the specified built-in to the standard output. * Returns Exit_SUCCESS if the built-in was found and the help was printed. * Returns Exit_FAILURE if the built-in was not found or an error occurred. */ int print_builtin_help_body(const wchar_t *name) { char *mbsname = malloc_wcstombs(name); const builtin_T *bi = get_builtin(mbsname); free(mbsname); if (bi == NULL) { xerror(0, Ngt("no such built-in `%ls'"), name); return Exit_FAILURE; } if (!xprintf("%ls: %s\n\n", name, gt(bi->help_text))) return Exit_FAILURE; /* TRANSLATORS: This is printed before syntax info of a built-in. */ if (!xprintf(gt("Syntax:\n%s\n"), gt(bi->syntax_text))) return Exit_FAILURE; if (wcscmp(name, L"set") == 0) { if (!print_shopts(false)) return Exit_FAILURE; } else { if (!print_builtin_options(bi->options)) return Exit_FAILURE; } return Exit_SUCCESS; } /* Prints a list of all shell options to the standard output. * Returns true iff successful. */ bool print_shopts(bool include_normal_options) { /* TRANSLATORS: This text is printed before a list of options. */ if (!xprintf(gt("Options:\n"))) return false; if (!print_shopts_body(include_normal_options)) return false; if (!xprintf("\n")) return false; return true; } /* Prints a list of options for a built-in to the standard output. * Returns true iff successful. */ bool print_builtin_options(const struct xgetopt_T *options) { if (options == NULL || options[0].shortopt == L'\0') return true; /* TRANSLATORS: This text is printed before a list of options. */ if (!xprintf(gt("Options:\n"))) return false; if (!print_option_list(options)) return false; if (!xprintf("\n")) return false; return true; } /* Prints a list of options to the standard output. * Returns true iff successful. */ bool print_option_list(const struct xgetopt_T *options) { bool print_short_option = there_is_any_short_option(options); xstrbuf_T line; sb_init(&line); for (const struct xgetopt_T *opt = options; opt->shortopt != L'\0'; opt++) { if (posixly_correct && !opt->posix) continue; sb_clear(&line); format_option_list_entry(opt, &line, print_short_option); if (!xprintf("%s\n", line.contents)) { sb_destroy(&line); return false; } } sb_destroy(&line); return true; } bool there_is_any_short_option(const struct xgetopt_T *options) { for (const struct xgetopt_T *opt = options; opt->shortopt != L'\0'; opt++) if (opt->shortopt != L'-') return true; return false; } /* Formats a line that describes the specified option. * The results are appended to buffer `buf'. `buf' must have been initialized * before calling this function. `buf' must be destroyed by the caller. * If `print_short_option' is false, the single-character option is omitted. */ void format_option_list_entry( const struct xgetopt_T *restrict opt, xstrbuf_T *restrict buf, bool print_short_option) { sb_ccat(buf, '\t'); if (print_short_option && opt->shortopt != L'-') { const char *INIT(optargmark, NULL); switch (opt->optarg) { case OPTARG_NONE: optargmark = ""; break; case OPTARG_REQUIRED: optargmark = " ..."; break; case OPTARG_OPTIONAL: optargmark = "[...]"; break; } sb_printf(buf, "-%lc%s", opt->shortopt, optargmark); } if (opt->longopt != NULL) { if (print_short_option && buf->length < 10) sb_ccat_repeat(buf, ' ', 10 - buf->length); const char *INIT(optargmark, NULL); switch (opt->optarg) { case OPTARG_NONE: optargmark = ""; break; case OPTARG_REQUIRED: optargmark = "=..."; break; case OPTARG_OPTIONAL: optargmark = "[=...]"; break; } sb_printf(buf, "--%ls%s", opt->longopt, optargmark); } } #endif /* YASH_ENABLE_HELP */ #if YASH_ENABLE_LINEEDIT /* Generates completion candidates for built-in command names matching the * pattern. The CGT_SBUILTIN, CGT_SSBUILTIN, and CGT_RBUILTIN flags in * `compopt->type' specify what candidate to generate. The other flags are * ignored. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_builtin_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_BUILTIN)) return; le_compdebug("adding built-in command name candidates"); if (!le_compile_cpatterns(compopt)) return; size_t i = 0; kvpair_T kv; while ((kv = ht_next(&builtins, &i)).key != NULL) { le_candgentype_T type; switch (((const builtin_T *) kv.value)->type) { case BI_SPECIAL: type = CGT_SBUILTIN; break; case BI_SEMISPECIAL: type = CGT_SSBUILTIN; break; case BI_REGULAR: type = CGT_RBUILTIN; break; default: assert(false); } if (!(compopt->type & type)) continue; if (le_match_comppatterns(compopt, kv.key)) le_new_candidate(CT_COMMAND, malloc_mbstowcs(kv.key), NULL, compopt); } } #endif /* YASH_ENABLE_LINEEDIT */ /* The ":"/"true" built-in. */ int true_builtin( int argc __attribute__((unused)), void **argv __attribute__((unused))) { return EXIT_SUCCESS; } /* The "false" built-in. */ int false_builtin( int argc __attribute__((unused)), void **argv __attribute__((unused))) { return EXIT_FAILURE; } #if YASH_ENABLE_HELP const char colon_help[] = Ngt( "do nothing" ); const char colon_syntax[] = Ngt( "\t: [...]\n" ); const char true_help[] = Ngt( "do nothing successfully" ); const char true_syntax[] = Ngt( "\ttrue\n" ); const char false_help[] = Ngt( "do nothing unsuccessfully" ); const char false_syntax[] = Ngt( "\tfalse\n" ); /* The "help" built-in. */ int help_builtin(int argc, void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, 0)) != NULL) { switch (opt->shortopt) { case L'-': goto print_help; default: return Exit_ERROR; } } if (xoptind == argc) print_help: return print_builtin_help(ARGV(0)); return print_builtin_helps(&argv[xoptind]); } const char help_help[] = Ngt( "print usage of built-in commands" ); const char help_syntax[] = Ngt( "\thelp [built-in...]\n" ); #endif /* YASH_ENABLE_HELP */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtin.h000066400000000000000000000046501354143602500142430ustar00rootroot00000000000000/* Yash: yet another shell */ /* builtin.h: built-in commands */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_BUILTIN_H #define YASH_BUILTIN_H #include typedef int main_T(int argc, void **argv) __attribute__((nonnull)); typedef enum builtintype_T { BI_SPECIAL, BI_SEMISPECIAL, BI_REGULAR, } builtintype_T; typedef struct builtin_T { main_T *body; builtintype_T type; #if YASH_ENABLE_HELP const char *help_text, *syntax_text; const struct xgetopt_T *options; #endif } builtin_T; extern void init_builtin(void); extern const builtin_T *get_builtin(const char *name) __attribute__((pure)); extern int mutually_exclusive_option_error(wchar_t opt1, wchar_t opt2); extern _Bool validate_operand_count(size_t count, size_t min, size_t max); extern int insufficient_operands_error(size_t min_required_operand_count); extern int too_many_operands_error(size_t max_accepted_operand_count); extern int special_builtin_error(int exitstatus); struct xgetopt_T; extern int print_builtin_help(const wchar_t *name) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern _Bool print_shopts(_Bool include_normal_options); extern _Bool print_option_list(const struct xgetopt_T *options) __attribute__((nonnull)); #endif extern int true_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char colon_help[], colon_syntax[], true_help[], true_syntax[]; #endif extern int false_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char false_help[], false_syntax[]; #endif extern int help_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char help_help[], help_syntax[]; #endif #endif /* YASH_BUILTIN_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/000077500000000000000000000000001354143602500142505ustar00rootroot00000000000000yash-2.49/builtins/Makefile.in000066400000000000000000000045701354143602500163230ustar00rootroot00000000000000# Makefile.in for yash: yet another shell # (C) 2007-2012 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . .POSIX: .SUFFIXES: .c .h .d .o .a @MAKE_SHELL@ topdir = .. subdir = builtins CC = @CC@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ LDFLAGS = @LDFLAGS@ LDLIBS = @LDLIBS@ AR = @AR@ ARFLAGS = @ARFLAGS@ SOURCES = printf.c test.c ulimit.c HEADERS = printf.h test.h ulimit.h PRINTF_OBJS = printf.o TEST_OBJS = test.o ULIMIT_OBJS = ulimit.o OBJS = @BUILTIN_OBJS@ TARGET = builtins.a YASH = @TARGET@ BYPRODUCTS = *.dSYM all: $(TARGET) .c.o: @rm -f $@ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< $(TARGET): $(OBJS) $(AR) $(ARFLAGS) $@ $(OBJS) DISTFILES = $(SOURCES) $(SOURCES:.c=.d) $(HEADERS) Makefile.in distfiles: makedeps $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(topdir)/$(DISTTARGETDIR) makedeps: _PHONY @(cd $(topdir) && $(MAKE) $(YASH)) $(topdir)/$(YASH) $(topdir)/makedeps.yash $(SOURCES) # ctags conforms to POSIX, but etags and cscope do not. CTAGS = @CTAGS@ CTAGSARGS = @CTAGSARGS@ ETAGS = @ETAGS@ ETAGSARGS = @ETAGSARGS@ CSCOPE = @CSCOPE@ CSCOPEARGS = @CSCOPEARGS@ tags: $(SOURCES) $(HEADERS) $(CTAGS) $(CTAGSARGS) TAGS: $(SOURCES) $(HEADERS) $(ETAGS) $(ETAGSARGS) cscope: cscope.out cscope.out: $(SOURCES) $(HEADERS) $(CSCOPE) $(CSCOPEARGS) mostlyclean: -rm -rf $(OBJS) $(BYPRODUCTS) clean: mostlyclean -rm -rf $(TARGET) distclean: clean -rm -rf Makefile tags TAGS cscope.out maintainer-clean: distclean -rm -rf $(SOURCES:.c=.d) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) .PHONY: all distfiles copy-distfiles makedeps cscope mostlyclean clean distclean maintainer-clean _PHONY: @MAKE_INCLUDE@ printf.d @MAKE_INCLUDE@ test.d @MAKE_INCLUDE@ ulimit.d yash-2.49/builtins/printf.c000066400000000000000000000467071354143602500157340ustar00rootroot00000000000000/* Yash: yet another shell */ /* printf.c: the echo/printf built-ins */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "printf.h" #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "../builtin.h" #include "../exec.h" #include "../option.h" #include "../strbuf.h" #include "../util.h" #include "../variable.h" #if HAVE_WCSTOLD && !defined(wcstold) extern long double wcstold(const wchar_t *restrict s, wchar_t **restrict endp) __attribute__((nonnull(1))); #endif /* type of format data used in the "printf" built-in */ struct format_T { struct format_T *next; enum formattype_T { FT_NONE, FT_RAW, FT_STRING, FT_CHAR, FT_INT, FT_UINT, FT_FLOAT, FT_ECHO, } type; union { struct { char *value; size_t length; } raw; char *convspec; struct { bool left; unsigned long width, max; } echo; } value; }; /* The FT_NONE format type corresponds to the "%%" conversion specification. * The FT_RAW format type is used for literal strings that are not conversion * specifications. The format types of FT_STRING, FT_CHAR, FT_INT, FT_UINT, and * FT_FLOAT are used for various types of conversion specifications (`convspec') * that require a value of the corresponding type. * The FT_ECHO format type is used for the "b" conversion specification. */ /* FT_STRING -> wchar_t * * FT_CHAR -> wint_t * FT_INT -> intmax_t * FT_UINT -> uintmax_t * FT_FLOAT -> long double */ enum printf_result_T { PR_OK, PR_OK_END, PR_ERROR, }; static enum printf_result_T echo_parse_escape(const wchar_t *restrict s, xstrbuf_T *restrict buf, mbstate_t *restrict st) __attribute__((nonnull)); static bool printf_parse_format( const wchar_t *format, struct format_T **resultp) __attribute__((nonnull)); static struct format_T **printf_parse_percent( const wchar_t **formatp, struct format_T **resultp) __attribute__((nonnull,warn_unused_result)); static struct format_T *printf_parse_percent_b(xstrbuf_T *convspec) __attribute__((nonnull,malloc,warn_unused_result)); static enum printf_result_T printf_printf( const struct format_T *format, const wchar_t *arg, xstrbuf_T *buf) __attribute__((nonnull(1,3))); static uintmax_t printf_parse_integer(const wchar_t *arg, bool is_signed); static enum printf_result_T printf_print_escape( const struct format_T *format, const wchar_t *arg, xstrbuf_T *buf) __attribute__((nonnull)); static void freeformat(struct format_T *f); /* The "echo" built-in. */ int echo_builtin(int argc, void **argv) { bool nonewline, escape, noption, eoption; const wchar_t *echo_style; /* Determine the behavior of "echo" according to $ECHO_STYLE. * The possible values for $ECHO_STYLE are: * SYSV, XSI, BSD, GNU, ZSH, DASH, RAW * But we only care about the first character of it. */ nonewline = false; echo_style = getvar(L VAR_ECHO_STYLE); switch ((echo_style != NULL) ? echo_style[0] : L'\0') { case L'S': case L's': case L'X': case L'x': default: escape = true, noption = false, eoption = false; break; case L'B': case L'b': escape = false, noption = true, eoption = false; break; case L'G': case L'g': escape = false, noption = true, eoption = true; break; case L'Z': case L'z': escape = true, noption = true, eoption = true; break; case L'D': case L'd': escape = true, noption = true, eoption = false; break; case L'R': case L'r': escape = false, noption = false, eoption = false; break; } /* parse options */ int index = 1; if (eoption) { assert(noption); for (index = 1; index < argc; index++) { if (ARGV(index)[0] != L'-' || ARGV(index)[1] == L'\0') break; if (ARGV(index)[wcsspn(&ARGV(index)[1], L"neE") + 1] != L'\0') break; for (const wchar_t *opt = &ARGV(index)[1]; *opt != L'\0'; opt++) { switch (*opt) { case L'n': nonewline = true; break; case L'e': escape = true; break; case L'E': escape = false; break; default: assert(false); } } } } else if (noption) { if (argc >= 2 && wcscmp(ARGV(index), L"-n") == 0) { nonewline = true; index++; } } /* parse arguments */ xstrbuf_T buf; mbstate_t state; sb_init(&buf); memset(&state, 0, sizeof state); // initialize as the initial shift state if (index < argc) { for (;;) { if (escape) { switch (echo_parse_escape(ARGV(index), &buf, &state)) { case PR_OK: break; case PR_OK_END: nonewline = true; goto print; case PR_ERROR: goto error; } } else { if (sb_wcscat(&buf, ARGV(index), &state) != NULL) { errno = EILSEQ; goto error; } } index++; if (index >= argc) break; if (!sb_wccat(&buf, L' ', &state)) goto error; } } if (!nonewline) if (!sb_wccat(&buf, L'\n', &state)) goto error; /* print to the standard output */ print: clearerr(stdout); fwrite(buf.contents, sizeof *buf.contents, buf.length, stdout); if (ferror(stdout)) goto error; if (fflush(stdout) != 0) goto error; sb_destroy(&buf); return Exit_SUCCESS; error: xerror(errno, Ngt("cannot print to the standard output")); sb_destroy(&buf); return Exit_FAILURE; } /* Parses string `s' that may include escape sequences. * The result is appended to string buffer `buf'. * Shift state `st' is used to convert wide characters into multibyte * characters. * On error, `errno' is set and PR_ERROR is returned. */ enum printf_result_T echo_parse_escape(const wchar_t *restrict s, xstrbuf_T *restrict buf, mbstate_t *restrict st) { while (*s != L'\0') { if (*s != L'\\') { normal: if (!sb_wccat(buf, *s, st)) return PR_ERROR; s++; } else { switch (s[1]) { wchar_t c; case L'a': c = L'\a'; goto print_char; case L'b': c = L'\b'; goto print_char; case L'c': return PR_OK_END; case L'e': c = L'\033'; goto print_char; case L'f': c = L'\f'; goto print_char; case L'n': c = L'\n'; goto print_char; case L'r': c = L'\r'; goto print_char; case L't': c = L'\t'; goto print_char; case L'v': c = L'\v'; goto print_char; case L'\\': c = L'\\'; goto print_char; print_char: if (!sb_wccat(buf, c, st)) return PR_ERROR; s += 2; break; /* At most three digits are recognized in an octal escape * excluding the first zero. */ case L'0': { int value = 0; s += 2; for (int i = 0; i < 3 && L'0' <= *s && *s <= L'7'; i++, s++) value = value * 8 + (*s - L'0'); sb_ccat(buf, TO_CHAR(value)); break; } default: goto normal; } } } return PR_OK; } #if YASH_ENABLE_HELP const char echo_help[] = Ngt( "print arguments" ); const char echo_syntax[] = Ngt( "\techo [string...]\n" ); #endif /* The "printf" built-in. */ int printf_builtin(int argc, void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (xoptind == argc) return insufficient_operands_error(1); /* parse the format string */ struct format_T *format = NULL; if (!printf_parse_format(ARGV(xoptind), &format)) { freeformat(format); return Exit_FAILURE; } xoptind++; /* format the operands */ int oldoptind; xstrbuf_T buf; sb_init(&buf); do { oldoptind = xoptind; for (struct format_T *f = format; f != NULL; f = f->next) { switch (printf_printf(f, ARGV(xoptind), &buf)) { case PR_OK: break; case PR_OK_END: goto print; case PR_ERROR: goto error; } } } while (xoptind < argc && xoptind != oldoptind); print: freeformat(format); /* print the result to the standard output */ clearerr(stdout); fwrite(buf.contents, sizeof *buf.contents, buf.length, stdout); if (ferror(stdout)) goto error; if (fflush(stdout) != 0) goto error; sb_destroy(&buf); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; error: xerror(errno, Ngt("cannot print to the standard output")); sb_destroy(&buf); return Exit_FAILURE; } /* Parses the format for the "printf" built-in. * If successful, a pointer to the result is assigned to `*resultp' and true is * returned. * If unsuccessful, an error message is printed and false is returned. A pointer * to a partial result may be assigned to `*resultp'. */ bool printf_parse_format(const wchar_t *format, struct format_T **resultp) { #define MAKE_STRING \ do { \ if (buf.length > 0) { \ struct format_T *f = xmalloc(sizeof *f); \ sb_wccat(&buf, L'\0', &state); \ f->next = NULL; \ f->type = FT_RAW; \ f->value.raw.length = buf.length; \ f->value.raw.value = sb_tostr(&buf); \ *resultp = f; \ resultp = &f->next; \ } else \ sb_destroy(&buf); \ } while (0) xstrbuf_T buf; mbstate_t state; sb_init(&buf); memset(&state, 0, sizeof state); while (*format != L'\0') { switch (*format) { case L'%': MAKE_STRING; resultp = printf_parse_percent(&format, resultp); if (resultp == NULL) return false; sb_init(&buf); break; case L'\\': switch (format[1]) { char c; case L'a': c = '\a'; goto put_char; case L'b': c = '\b'; goto put_char; case L'f': c = '\f'; goto put_char; case L'n': c = '\n'; goto put_char; case L'r': c = '\r'; goto put_char; case L't': c = '\t'; goto put_char; case L'v': c = '\v'; goto put_char; case L'\\': c = '\\'; goto put_char; case L'\"': c = '\"'; goto put_char; case L'\'': c = '\''; goto put_char; put_char: sb_ccat(&buf, c); format += 2; break; /* At most three digits are recognized in an octal escape */ case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': { int value = 0; format++; for (int i = 0; i < 3 && L'0' <= *format && *format <= L'7'; i++, format++) value = value * 8 + (*format - L'0'); sb_ccat(&buf, TO_CHAR(value)); break; } default: goto normal; } break; default: normal: { if (!sb_wccat(&buf, *format, &state)) { xerror(errno, Ngt("cannot parse the format")); sb_destroy(&buf); return false; } format++; break; } } } MAKE_STRING; return true; #undef MAKE_STRING } /* Parses the conversion specification that starts with L'%' pointed to by * `*formatp'. * If successful, a pointer to the character to parse next is assigned to * `*formatp', a pointer to the result is assigned to `*resultp', and the next * `resultp' value is returned. * If unsuccessful, an error message is printed and NULL is returned. A pointer * to a partial result may be assigned to `*resultp'. */ struct format_T **printf_parse_percent( const wchar_t **formatp, struct format_T **resultp) { const wchar_t *format = *formatp; xstrbuf_T buf; bool hashflag = false, zeroflag = false; enum formattype_T type; struct format_T *result; #ifdef __STDC_MB_MIGHT_NEQ_WC__ mbstate_t state; memset(&state, 0, sizeof state); # define BUFCAT(c) sb_wccat(&buf, c, &state) #else # define BUFCAT(c) sb_ccat(&buf, (char) (c)) #endif assert(*format == L'%'); format++; sb_init(&buf); sb_ccat(&buf, '%'); /* parse flags */ for (;;) { switch (*format) { case L'#': hashflag = true; goto add_char; case L'0': zeroflag = true; goto add_char; case L'-': case L'+': case L' ': add_char: BUFCAT(*format++); break; default: goto parse_width; } } parse_width: while (iswdigit(*format)) BUFCAT(*format++); /* parse precision */ if (*format == L'.') { do { BUFCAT(*format++); } while (iswdigit(*format)); } /* parse conversion specifier */ switch (*format) { case L'd': case L'i': if (hashflag) goto flag_error; type = FT_INT; sb_ccat(&buf, 'j'); break; case L'u': if (hashflag) goto flag_error; /* falls thru! */ case L'o': case L'x': case L'X': type = FT_UINT; sb_ccat(&buf, 'j'); break; case L'f': case L'F': case L'e': case L'E': case L'g': case L'G': type = FT_FLOAT; sb_ccat(&buf, 'L'); break; case L'c': if (hashflag || zeroflag) goto flag_error; type = FT_CHAR; sb_ccat(&buf, 'l'); break; case L's': if (hashflag || zeroflag) goto flag_error; type = FT_STRING; sb_ccat(&buf, 'l'); break; case L'b': if (hashflag || zeroflag) goto flag_error; format++; result = printf_parse_percent_b(&buf); goto end; case L'%': if (buf.length != 1) goto flag_error; type = FT_NONE; break; case L'\0': xerror(0, Ngt("the conversion specifier is missing")); sb_destroy(&buf); return NULL; default: xerror(0, Ngt("`%lc' is not a valid conversion specifier"), (wint_t) *format); sb_destroy(&buf); return NULL; flag_error: xerror(0, Ngt("invalid flag for conversion specifier `%lc'"), (wint_t) *format); sb_destroy(&buf); return NULL; } BUFCAT(*format++); result = xmalloc(sizeof *result); result->next = NULL; result->type = type; switch (type) { case FT_NONE: sb_destroy(&buf); break; case FT_RAW: case FT_ECHO: assert(false); default: result->value.convspec = sb_tostr(&buf); break; } end: *formatp = format; *resultp = result; return &result->next; #undef BUFCAT } /* Parses the conversion specification given in buffer `convspec'. * The specification in the buffer must not have the conversion specifier, which * is assumed to be 'b'. The buffer is destroyed in this function. */ struct format_T *printf_parse_percent_b(xstrbuf_T *convspec) { size_t index = 0; struct format_T *result = xmalloc(sizeof *result); result->next = NULL; result->type = FT_ECHO; result->value.echo.left = false; assert(convspec->contents[index] == '%'); for (;;) { index++; switch (convspec->contents[index]) { case '#': case '0': case '+': case ' ': break; case '-': result->value.echo.left = true; break; default: goto parse_width; } } char *endp; parse_width: result->value.echo.width = strtoul(convspec->contents + index, &endp, 10); index = endp - convspec->contents; if (convspec->contents[index] == '.') { index++; result->value.echo.max = strtoul(convspec->contents + index, &endp, 10); index = endp - convspec->contents; } else { result->value.echo.max = ULONG_MAX; } assert(index == convspec->length); sb_destroy(convspec); return result; } /* Formats the specified string. The result is appended to buffer `buf'. * Increases `xoptind' if `arg' is used. Otherwise, `arg' is ignored. */ enum printf_result_T printf_printf( const struct format_T *format, const wchar_t *arg, xstrbuf_T *buf) { switch (format->type) { case FT_NONE: sb_ccat(buf, '%'); return PR_OK; case FT_RAW: sb_ncat_force(buf, format->value.raw.value, format->value.raw.length); return PR_OK; case FT_STRING: if (arg != NULL) xoptind++; else arg = L""; if (sb_printf(buf, format->value.convspec, arg) < 0) return PR_ERROR; return PR_OK; case FT_CHAR: if (arg != NULL && arg[0] != L'\0') { xoptind++; if (sb_printf(buf, format->value.convspec, (wint_t) arg[0]) < 0) return PR_ERROR; } return PR_OK; case FT_INT: if (sb_printf(buf, format->value.convspec, printf_parse_integer(arg, true)) < 0) return PR_ERROR; return PR_OK; case FT_UINT: if (sb_printf(buf, format->value.convspec, printf_parse_integer(arg, false)) < 0) return PR_ERROR; return PR_OK; case FT_FLOAT: { long double value; wchar_t *end; if (arg != NULL) xoptind++; else arg = L"0"; errno = 0; #if HAVE_WCSTOLD if (!posixly_correct) value = wcstold(arg, &end); else #endif value = wcstod(arg, &end); if (errno || arg[0] == L'\0' || *end != L'\0') xerror(errno, Ngt("`%ls' is not a valid number"), arg); if (sb_printf(buf, format->value.convspec, value) < 0) return PR_ERROR; return PR_OK; } case FT_ECHO: if (arg != NULL) xoptind++; else arg = L""; return printf_print_escape(format, arg, buf); } assert(false); } /* Parses the specified string as an integer. */ uintmax_t printf_parse_integer(const wchar_t *arg, bool is_signed) { uintmax_t value; wchar_t *end; if (arg != NULL) xoptind++; else arg = L"0"; if (arg[0] == L'"' || arg[0] == L'\'') { value = (uintmax_t) arg[1]; } else { errno = 0; if (is_signed) value = (uintmax_t) wcstoimax(arg, &end, 0); else value = wcstoumax(arg, &end, 0); if (errno || arg[0] == L'\0' || *end != L'\0') xerror(errno, Ngt("`%ls' is not a valid integer"), arg); } return value; } /* Prints the specified string that may include escape sequences and formats it * in the specified format. */ enum printf_result_T printf_print_escape( const struct format_T *format, const wchar_t *s, xstrbuf_T *buf) { xstrbuf_T subbuf; mbstate_t state; sb_init(&subbuf); memset(&state, 0, sizeof state); enum printf_result_T result = echo_parse_escape(s, &subbuf, &state); if (result == PR_OK) sb_wccat(&subbuf, L'\0', &state); if (format->value.echo.max < subbuf.length) sb_truncate(&subbuf, format->value.echo.max); if (format->value.echo.width <= subbuf.length) { sb_ncat_force(buf, subbuf.contents, subbuf.length); } else { size_t increment = format->value.echo.width - subbuf.length; if (format->value.echo.left) { sb_ncat_force(buf, subbuf.contents, subbuf.length); sb_ccat_repeat(buf, ' ', increment); } else { sb_ccat_repeat(buf, ' ', increment); sb_ncat_force(buf, subbuf.contents, subbuf.length); } } sb_destroy(&subbuf); return result; } /* Frees the specified format data. */ void freeformat(struct format_T *f) { while (f != NULL) { struct format_T *next = f->next; switch (f->type) { case FT_NONE: break; case FT_RAW: free(f->value.raw.value); break; case FT_ECHO: break; default: free(f->value.convspec); break; } free(f); f = next; } } #if YASH_ENABLE_HELP const char printf_help[] = Ngt( "print a formatted string" ); const char printf_syntax[] = Ngt( "\tprintf format [value...]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/printf.h000066400000000000000000000022351354143602500157250ustar00rootroot00000000000000/* Yash: yet another shell */ /* printf.h: echo/printf built-ins */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_PRINTF_H #define YASH_PRINTF_H extern int echo_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char echo_help[], echo_syntax[]; #endif extern int printf_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char printf_help[], printf_syntax[]; #endif #endif /* YASH_PRINTF_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/test.c000066400000000000000000000534231354143602500154020ustar00rootroot00000000000000/* Yash: yet another shell */ /* test.c: test builtin */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "test.h" #include #include #include #include #include #include #include #include #include #if YASH_ENABLE_DOUBLE_BRACKET # include "../expand.h" #endif #include "../option.h" #if YASH_ENABLE_DOUBLE_BRACKET # include "../parser.h" #endif #include "../path.h" #include "../plist.h" #include "../strbuf.h" #include "../util.h" #include "../xfnmatch.h" #define Exit_TRUE 0 /* Exit_SUCCESS */ #define Exit_FALSE 1 /* Exit_FAILURE */ #define Exit_TESTERROR 2 /* Exit_ERROR */ struct test_state { void **args; int argc; int index; }; enum filecmp { FC_ID, FC_SAME, FC_NEWER, FC_OLDER, FC_UNKNOWN, }; static inline bool test_single(void *args[static 1]); static bool test_double(void *args[static 2]); static bool test_file(wchar_t type, const char *file) __attribute__((nonnull)); static bool test_triple(void *args[static 3]); static bool test_long_or(struct test_state *state) __attribute__((nonnull)); static bool test_long_and(struct test_state *state) __attribute__((nonnull)); static bool test_long_term(struct test_state *state) __attribute__((nonnull)); static bool is_term_delimiter(const wchar_t *word) __attribute__((nonnull,pure)); static int compare_integers(const wchar_t *left, const wchar_t *right) __attribute__((nonnull,pure)); static int compare_versions(const wchar_t *left, const wchar_t *right) __attribute__((nonnull)); static enum filecmp compare_files(const wchar_t *left, const wchar_t *right) __attribute__((nonnull)); #if YASH_ENABLE_DOUBLE_BRACKET static int eval_dbexp(const dbexp_T *e) __attribute__((nonnull)); static inline wchar_t *expand_double_bracket_operand(const wordunit_T *w) __attribute__((nonnull,malloc,warn_unused_result)); static wchar_t *expand_and_unescape_double_bracket_operand(const wordunit_T *w) __attribute__((nonnull,malloc,warn_unused_result)); static bool test_triple_db( const wchar_t *lhs, const wchar_t *op, const wchar_t *rhs_escaped) __attribute__((nonnull)); static bool test_triple_args( const wchar_t *left, const wchar_t *op, const wchar_t *right) __attribute__((nonnull)); #endif /* The "test" ("[") built-in. */ int test_builtin(int argc, void **argv) { if (wcscmp(ARGV(0), L"[") == 0) { argc--; if (wcscmp(ARGV(argc), L"]") != 0) { xerror(0, Ngt("`%ls' is missing"), L"]"); return Exit_TESTERROR; } } assert(argc > 0); argc--, argv++; struct test_state state; bool result; switch (argc) { case 0: result = false; break; case 1: result = test_single(argv); break; case 2: result = test_double(argv); break; case 3: result = test_triple(argv); break; case 4: if (wcscmp(argv[0], L"!") == 0) { result = !test_triple(&argv[1]); break; } if (wcscmp(argv[0], L"(") == 0 && wcscmp(argv[3], L")") == 0) { result = test_double(&argv[1]); break; } /* falls thru! */ default: state.args = argv; state.argc = argc; state.index = 0; result = test_long_or(&state); if (yash_error_message_count == 0 && state.index < state.argc) xerror(0, Ngt("`%ls' is not a valid operator"), (const wchar_t *) state.args[state.index]); break; } if (yash_error_message_count > 0) return Exit_TESTERROR; return result ? Exit_TRUE : Exit_FALSE; } /* Tests the specified one-token expression. */ bool test_single(void *args[static 1]) { const wchar_t *arg0 = args[0]; return arg0[0] != L'\0'; } /* Tests the specified two-token expression. */ bool test_double(void *args[static 2]) { const wchar_t *op = args[0], *arg = args[1]; if (wcscmp(op, L"!") == 0) return !test_single(&args[1]); if (!is_unary_primary(op)) { xerror(0, Ngt("`%ls' is not a unary operator"), op); return 0; } switch (op[1]) { case L'n': return arg[0] != L'\0'; case L'z': return arg[0] == L'\0'; case L't': { int fd; return xwcstoi(arg, 10, &fd) && isatty(fd); } case L'o': if (arg[0] == L'?') return is_valid_option_name(&arg[1]); else return option_is_enabled(arg); } char *mbsarg = malloc_wcstombs(arg); if (mbsarg == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return 0; } bool result = test_file(op[1], mbsarg); free(mbsarg); return result; } /* An auxiliary function for file type checking. */ bool test_file(wchar_t type, const char *file) { switch (type) { case L'd': return is_directory(file); case L'e': return is_file(file); case L'f': return is_regular_file(file); case L'r': return is_readable(file); case L'w': return is_writable(file); case L'x': return is_executable(file); } struct stat st; switch (type) { case L'h': case L'L': return (lstat(file, &st) == 0) && S_ISLNK(st.st_mode); #if !HAVE_S_ISVTX case L'k': return false; #endif } if (stat(file, &st) < 0) return false; switch (type) { case L'b': return S_ISBLK(st.st_mode); case L'c': return S_ISCHR(st.st_mode); case L'G': return st.st_gid == getegid(); case L'g': return st.st_mode & S_ISGID; #if HAVE_S_ISVTX case L'k': return st.st_mode & S_ISVTX; #endif case L'N': return st.st_atime < st.st_mtime #if HAVE_ST_ATIM && HAVE_ST_MTIM || (st.st_atime == st.st_mtime && st.st_atim.tv_nsec < st.st_mtim.tv_nsec) #elif HAVE_ST_ATIMESPEC && HAVE_ST_MTIMESPEC || (st.st_atime == st.st_mtime && st.st_atimespec.tv_nsec < st.st_mtimespec.tv_nsec) #elif HAVE_ST_ATIMENSEC && HAVE_ST_MTIMENSEC || (st.st_atime == st.st_mtime && st.st_atimensec < st.st_mtimensec) #elif HAVE___ST_ATIMENSEC && HAVE___ST_MTIMENSEC || (st.st_atime == st.st_mtime && st.__st_atimensec < st.__st_mtimensec) #endif ; case L'O': return st.st_uid == geteuid(); case L'p': return S_ISFIFO(st.st_mode); case L'S': return S_ISSOCK(st.st_mode); case L's': return st.st_size > 0; case L'u': return st.st_mode & S_ISUID; } assert(false); } /* Tests the specified three-token expression. */ bool test_triple(void *args[static 3]) { const wchar_t *left = args[0], *op = args[1], *right = args[2]; switch (op[0]) { case L'=': if (op[1] == L'\0' || (op[1] == L'=' && op[2] == L'\0')) return wcscmp(left, right) == 0; if (op[1] == L'=' && op[2] == L'=' && op[3] == L'\0') return wcscoll(left, right) == 0; if (op[1] == L'~' && op[2] == L'\0') return match_regex(left, right); goto not_binary; case L'!': if (op[1] == L'=' && op[2] == L'\0') return wcscmp(left, right) != 0; if (op[1] == L'=' && op[2] == L'=' && op[3] == L'\0') return wcscoll(left, right) != 0; goto not_binary; case L'<': if (op[1] == L'\0') return wcscoll(left, right) < 0; if (op[1] == L'=' && op[2] == L'\0') return wcscoll(left, right) <= 0; goto not_binary; case L'>': if (op[1] == L'\0') return wcscoll(left, right) > 0; if (op[1] == L'=' && op[2] == L'\0') return wcscoll(left, right) >= 0; goto not_binary; case L'-': break; default: goto not_binary; } assert(op[0] == L'-'); switch (op[1]) { case L'a': if (op[2] == L'\0') return test_single(args) && test_single(&args[2]); break; case L'o': if (op[2] == L'\0') return test_single(args) || test_single(&args[2]); if (op[2] == L't') if (op[3] == L'\0') return compare_files(left, right) == FC_OLDER; break; case L'e': switch (op[2]) { case L'f': if (op[3] == L'\0') return compare_files(left, right) == FC_ID; break; case L'q': if (op[3] == L'\0') return compare_integers(left, right) == 0; break; } break; case L'n': switch (op[2]) { case L'e': if (op[3] == L'\0') return compare_integers(left, right) != 0; break; case L't': if (op[3] == L'\0') return compare_files(left, right) == FC_NEWER; break; } break; case L'g': switch (op[2]) { case L't': if (op[3] == L'\0') return compare_integers(left, right) > 0; break; case L'e': if (op[3] == L'\0') return compare_integers(left, right) >= 0; break; } break; case L'l': switch (op[2]) { case L't': if (op[3] == L'\0') return compare_integers(left, right) < 0; break; case L'e': if (op[3] == L'\0') return compare_integers(left, right) <= 0; break; } break; case L'v': switch (op[2]) { case L'e': if (op[3] == L'q' && op[4] == L'\0') return compare_versions(left, right) == 0; break; case L'n': if (op[3] == L'e' && op[4] == L'\0') return compare_versions(left, right) != 0; break; case L'g': switch (op[3]) { case L't': if (op[4] == L'\0') return compare_versions(left, right) > 0; break; case L'e': if (op[4] == L'\0') return compare_versions(left, right) >= 0; break; } break; case L'l': switch (op[3]) { case L't': if (op[4] == L'\0') return compare_versions(left, right) < 0; break; case L'e': if (op[4] == L'\0') return compare_versions(left, right) <= 0; break; } break; } break; } not_binary: if (wcscmp(left, L"!") == 0) return !test_double(&args[1]); if (wcscmp(left, L"(") == 0 && wcscmp(right, L")") == 0) return test_single(&args[1]); xerror(0, Ngt("`%ls' is not a binary operator"), op); return 0; } /* exp := exp "-o" and | and * and := and "-a" term | term * term := "(" exp ")" | "!" "(" exp ")" | single | double | triple */ /* Tests the specified long expression using `state'. */ bool test_long_or(struct test_state *state) { bool result; result = test_long_and(state); while (yash_error_message_count == 0 && state->index < state->argc && wcscmp(state->args[state->index], L"-o") == 0) { state->index++; result |= test_long_and(state); } return result; } /* Tests the specified long expression using `state'. */ bool test_long_and(struct test_state *state) { bool result; result = test_long_term(state); while (yash_error_message_count == 0 && state->index < state->argc && wcscmp(state->args[state->index], L"-a") == 0) { state->index++; result &= test_long_term(state); } return result; } /* Tests the specified long expression using `state'. */ bool test_long_term(struct test_state *state) { bool result; bool negate = false; if (state->index < state->argc && wcscmp(state->args[state->index], L"!") == 0) { state->index++; negate = true; } if (state->index >= state->argc) { assert(state->argc > 0); xerror(0, Ngt("an expression is missing after `%ls'"), (const wchar_t *) state->args[state->index - 1]); return 0; } if (wcscmp(state->args[state->index], L"(") == 0) { state->index++; result = test_long_or(state); if (state->index >= state->argc || wcscmp(state->args[state->index], L")") != 0) { xerror(0, Ngt("`%ls' is missing"), L")"); return 0; } state->index++; } else if (state->index + 3 <= state->argc && is_binary_primary(state->args[state->index + 1]) && (state->index + 3 >= state->argc || is_term_delimiter(state->args[state->index + 3]))) { result = test_triple(&state->args[state->index]); state->index += 3; } else if (state->index + 2 <= state->argc && is_unary_primary(state->args[state->index]) && (state->index + 2 >= state->argc || is_term_delimiter(state->args[state->index + 2]))) { result = test_double(&state->args[state->index]); state->index += 2; } else { result = test_single(&state->args[state->index]); state->index += 1; } return result ^ negate; } /* Checks if `word' is a unary primary operator. */ /* Note that "!" is not a primary operator. */ bool is_unary_primary(const wchar_t *word) { if (word[0] != L'-' || word[1] == L'\0' || word[2] != L'\0') return false; switch (word[1]) { case L'b': case L'c': case L'd': case L'e': case L'f': case L'G': case L'g': case L'h': case L'k': case L'L': case L'N': case L'n': case L'O': case L'o': case L'p': case L'r': case L'S': case L's': case L't': case L'u': case L'w': case L'x': case L'z': return true; default: return false; } } /* Checks if `word' is a binary primary operator. * This function returns false for "-a" and "-o". */ bool is_binary_primary(const wchar_t *word) { switch (word[0]) { case L'=': if (word[1] == L'\0' || (word[1] == L'~' && word[2] == L'\0')) return true; /* falls thru! */ case L'!': if (word[1] != L'=') return false; return (word[2] == L'\0') || (word[2] == L'=' && word[3] == L'\0'); case L'<': case L'>': return (word[1] == L'\0') || (word[1] == L'=' && word[2] == L'\0'); case L'-': break; default: return false; } assert(word[0] == L'-'); switch (word[1]) { case L'e': switch (word[2]) { case L'f': case L'q': return word[3] == L'\0'; } break; case L'n': case L'g': case L'l': switch (word[2]) { case L't': case L'e': return word[3] == L'\0'; } break; case L'o': return word[2] == L't' && word[3] == L'\0'; case L'v': switch (word[2]) { case L'e': return word[3] == L'q' && word[4] == L'\0'; case L'n': return word[3] == L'e' && word[4] == L'\0'; case L'g': case L'l': switch (word[3]) { case L't': case L'e': return word[4] == L'\0'; } break; } break; } return false; } /* Checks if `word' is a term delimiter: * one of ")", "-a", "-o". */ bool is_term_delimiter(const wchar_t *word) { switch (word[0]) { case L')': return word[1] == L'\0'; case L'-': switch (word[1]) { case L'a': case L'o': return word[2] == L'\0'; } break; } return false; } /* Converts the specified two strings into integers and compares them. * Returns -1, 0, 1 if the first integer is less than, equal to, or greater than * the second, respectively. */ int compare_integers(const wchar_t *left, const wchar_t *right) { intmax_t il, ir; wchar_t *end; errno = 0; il = wcstoimax(left, &end, 10); if (errno != 0 || left[0] == L'\0' || *end != L'\0') { xerror(errno, Ngt("`%ls' is not a valid integer"), left); return 0; } errno = 0; ir = wcstoimax(right, &end, 10); if (errno != 0 || right[0] == L'\0' || *end != L'\0') { xerror(errno, Ngt("`%ls' is not a valid integer"), right); return 0; } if (il < ir) return -1; else if (il > ir) return 1; else return 0; } /* Compares the specified two strings as version numbers. * Returns a value less than, equal to, and greater than zero if the first is * less than, equal to, and greater than the second, respectively. */ int compare_versions(const wchar_t *left, const wchar_t *right) { for (;;) { bool leftisdigit = iswdigit(*left), rightisdigit = iswdigit(*right); if (leftisdigit && rightisdigit) { uintmax_t il, ir; il = wcstoumax(left, (wchar_t **) &left, 10); ir = wcstoumax(right, (wchar_t **) &right, 10); if (il > ir) return 1; if (il < ir) return -1; } else if (leftisdigit) { return 1; } else if (rightisdigit) { return -1; } bool leftisalnum = iswalnum(*left), rightisalnum = iswalnum(*right); if (leftisalnum && !rightisalnum) return 1; if (!leftisalnum && rightisalnum) return -1; if (*left != *right) return wcscoll(left, right); if (*left == L'\0') return 0; left++, right++; } } /* Compares the specified two files. * Returns one of the followings: * FC_ID: the two files have the same inode * FC_SAME: different inodes, the same modification time * FC_NEWER: `left' has the modification time newer than `right' * FC_OLDER: `left' has the modification time older than `right' * FC_UNKNOWN: comparison error (neither file is `stat'able) * If either (but not both) file is not `stat'able, the `stat'able one is * considered newer. */ enum filecmp compare_files(const wchar_t *left, const wchar_t *right) { char *mbsfile; struct stat sl, sr; bool sl_ok, sr_ok; mbsfile = malloc_wcstombs(left); if (mbsfile == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return FC_UNKNOWN; } sl_ok = stat(mbsfile, &sl) >= 0; free(mbsfile); mbsfile = malloc_wcstombs(right); if (mbsfile == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return FC_UNKNOWN; } sr_ok = stat(mbsfile, &sr) >= 0; free(mbsfile); if (!sl_ok) if (!sr_ok) return FC_UNKNOWN; else return FC_OLDER; else if (!sr_ok) return FC_NEWER; if (stat_result_same_file(&sl, &sr)) return FC_ID; else if (sl.st_mtime < sr.st_mtime) return FC_OLDER; else if (sl.st_mtime > sr.st_mtime) return FC_NEWER; #if HAVE_ST_MTIM else if (sl.st_mtim.tv_nsec < sr.st_mtim.tv_nsec) return FC_OLDER; else if (sl.st_mtim.tv_nsec > sr.st_mtim.tv_nsec) return FC_NEWER; #elif HAVE_ST_MTIMESPEC else if (sl.st_mtimespec.tv_nsec < sr.st_mtimespec.tv_nsec) return FC_OLDER; else if (sl.st_mtimespec.tv_nsec > sr.st_mtimespec.tv_nsec) return FC_NEWER; #elif HAVE_ST_MTIMENSEC else if (sl.st_mtimensec < sr.st_mtimensec) return FC_OLDER; else if (sl.st_mtimensec > sr.st_mtimensec) return FC_NEWER; #elif HAVE___ST_MTIMENSEC else if (sl.__st_mtimensec < sr.__st_mtimensec) return FC_OLDER; else if (sl.__st_mtimensec > sr.__st_mtimensec) return FC_NEWER; #endif else return FC_SAME; } #if YASH_ENABLE_HELP const char test_help[] = Ngt( "evaluate a conditional expression" ); const char test_syntax[] = Ngt( "\ttest expression\n" "\t[ expression ]\n" ); #endif #if YASH_ENABLE_DOUBLE_BRACKET /* Executes the double-bracket command, evaluating the conditional expression. * Returns the exit status. Prints an error message on error. */ int exec_double_bracket(const command_T *c) { assert(c->c_type == CT_BRACKET); yash_error_message_count = 0; return eval_dbexp(c->c_dbexp); } /* Evaluates the expression of a double-bracket command. * You must reset `yash_error_message_count' before calling this function. * Returns the exit status. Prints an error message on error. */ int eval_dbexp(const dbexp_T *e) { int lhs_result; bool result; wchar_t *lhs = NULL, *rhs = NULL; switch (e->type) { case DBE_OR: lhs_result = eval_dbexp(e->lhs.subexp); if (lhs_result != Exit_FALSE) return lhs_result; return eval_dbexp(e->rhs.subexp); case DBE_AND: lhs_result = eval_dbexp(e->lhs.subexp); if (lhs_result != Exit_TRUE) return lhs_result; return eval_dbexp(e->rhs.subexp); case DBE_NOT: switch (eval_dbexp(e->rhs.subexp)) { case Exit_TRUE: return Exit_FALSE; case Exit_FALSE: return Exit_TRUE; default: return Exit_TESTERROR; } case DBE_UNARY: rhs = expand_and_unescape_double_bracket_operand(e->rhs.word); if (rhs == NULL) return Exit_TESTERROR; result = test_double((void *[]) { e->operator, rhs }); break; case DBE_BINARY: lhs = expand_and_unescape_double_bracket_operand(e->lhs.word); if (lhs == NULL) return Exit_TESTERROR; rhs = expand_double_bracket_operand(e->rhs.word); if (rhs == NULL) return Exit_TESTERROR; result = test_triple_db(lhs, e->operator, rhs); break; case DBE_STRING: rhs = expand_and_unescape_double_bracket_operand(e->rhs.word); if (rhs == NULL) return Exit_TESTERROR; result = test_single((void *[]) { rhs }); break; default: assert(false); } free(lhs); free(rhs); if (yash_error_message_count > 0) return Exit_TESTERROR; return result ? Exit_TRUE : Exit_FALSE; } /* Expands the operand of a primary. * The result may contain backslash escapes. */ wchar_t *expand_double_bracket_operand(const wordunit_T *w) { return expand_single(w, TT_SINGLE, true, false); } /* Expands the operand of a primary. * The result is literal (does not contain backslash escapes). */ wchar_t *expand_and_unescape_double_bracket_operand(const wordunit_T *w) { wchar_t *e = expand_double_bracket_operand(w); if (e == NULL) return NULL; return unescapefree(e); } /* Tests the specified three-token (binary) primary in the double-bracket * command. The left-hand-side must be given literal while the right-hand-side * backslash-escaped. */ bool test_triple_db( const wchar_t *lhs, const wchar_t *op, const wchar_t *rhs_escaped) { /* Some string comparison primaries in the double-bracket command are * different from those in the test built-in. */ switch (op[0]) { case L'=': if (op[1] == L'~') { assert(op[2] == L'\0'); return test_triple_args(lhs, op, rhs_escaped); } if (op[1] == L'\0' || (op[1] == L'=' && op[2] == L'\0')) return match_pattern(lhs, rhs_escaped); break; case L'!': assert(op[1] == L'='); if (op[2] == L'\0') return !match_pattern(lhs, rhs_escaped); break; } wchar_t *rhs = unescape(rhs_escaped); bool result = test_triple_args(lhs, op, rhs); free(rhs); return result; } /* Tests the specified three-token expression. */ bool test_triple_args( const wchar_t *left, const wchar_t *op, const wchar_t *right) { void *args[] = { (void *) left, (void *) op, (void *) right, }; return test_triple(args); } #endif /* YASH_ENABLE_DOUBLE_BRACKET */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/test.h000066400000000000000000000025021354143602500153770ustar00rootroot00000000000000/* Yash: yet another shell */ /* test.h: test builtin */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_TEST_H #define YASH_TEST_H #include extern int test_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char test_help[], test_syntax[]; #endif extern _Bool is_unary_primary(const wchar_t *word) __attribute__((nonnull,pure)); extern _Bool is_binary_primary(const wchar_t *word) __attribute__((nonnull,pure)); #if YASH_ENABLE_DOUBLE_BRACKET struct command_T; extern int exec_double_bracket(const struct command_T *c) __attribute__((nonnull)); #endif #endif /* YASH_TEST_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/ulimit.c000066400000000000000000000200341354143602500157160ustar00rootroot00000000000000/* Yash: yet another shell */ /* ulimit.c: ulimit builtin */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "ulimit.h" #include #include #include #ifdef HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "../builtin.h" #include "../exec.h" #include "../util.h" /* Including is required before including on * FreeBSD, but is automatically included in . */ /* On old Mac OS X, depends on but does not include * . We have to include it manually. */ #if !HAVE_RLIM_SAVED_MAX # define RLIM_SAVED_MAX RLIM_INFINITY #endif #if !HAVE_RLIM_SAVED_CUR # define RLIM_SAVED_CUR RLIM_INFINITY #endif struct resource { int type; rlim_t factor; const char *description; }; static int print_all_limits(bool soft); static void print_limit_value(bool soft, const struct rlimit *rlimit, const struct resource *resource) __attribute__((nonnull)); #define RES(type,factor,desc) \ (struct resource) { type, factor, desc, } static const struct resource res_fsize = { RLIMIT_FSIZE, 512, Ngt("file size (blocks)") }; const struct xgetopt_T ulimit_options[] = { { L'H', L"hard", OPTARG_NONE, true, NULL, }, { L'S', L"soft", OPTARG_NONE, true, NULL, }, { L'a', L"all", OPTARG_NONE, true, NULL, }, { L'c', L"core", OPTARG_NONE, true, &RES(RLIMIT_CORE, 512, Ngt("core file size (blocks)")), }, { L'd', L"data", OPTARG_NONE, true, &RES(RLIMIT_DATA, 1024, Ngt("data segment size (kbytes)")), }, #if HAVE_RLIMIT_NICE { L'e', L"nice", OPTARG_NONE, true, &RES(RLIMIT_NICE, 1, Ngt("max nice")), }, #endif { L'f', L"fsize", OPTARG_NONE, true, (void *) &res_fsize, }, #if HAVE_RLIMIT_SIGPENDING { L'i', L"sigpending", OPTARG_NONE, true, &RES(RLIMIT_SIGPENDING, 1, Ngt("pending signals")), }, #endif #if HAVE_RLIMIT_MEMLOCK { L'l', L"memlock", OPTARG_NONE, true, &RES(RLIMIT_MEMLOCK, 1024, Ngt("locked memory (kbytes)")), }, #endif #if HAVE_RLIMIT_RSS { L'm', L"rss", OPTARG_NONE, true, &RES(RLIMIT_RSS, 1024, Ngt("resident set size (kbytes)")), }, #endif { L'n', L"nofile", OPTARG_NONE, true, &RES(RLIMIT_NOFILE, 1, Ngt("open files")), }, #if HAVE_RLIMIT_MSGQUEUE { L'q', L"msgqueue", OPTARG_NONE, true, &RES(RLIMIT_MSGQUEUE, 1, Ngt("message queue size (bytes)")), }, #endif #if HAVE_RLIMIT_RTPRIO { L'r', L"rtprio", OPTARG_NONE, true, &RES(RLIMIT_RTPRIO, 1, Ngt("real-time priority")), }, #endif { L's', L"stack", OPTARG_NONE, true, &RES(RLIMIT_STACK, 1024, Ngt("stack size (kbytes)")), }, { L't', L"cpu", OPTARG_NONE, true, &RES(RLIMIT_CPU, 1, Ngt("CPU time (seconds)")), }, #if HAVE_RLIMIT_NPROC { L'u', L"nproc", OPTARG_NONE, true, &RES(RLIMIT_NPROC, 1, Ngt("user processes")), }, #endif #if HAVE_RLIMIT_AS { L'v', L"as", OPTARG_NONE, true, &RES(RLIMIT_AS, 1024, Ngt("memory (kbytes)")), }, #endif #if HAVE_RLIMIT_LOCKS { L'x', L"locks", OPTARG_NONE, true, &RES(RLIMIT_LOCKS, 1, Ngt("file locks")), }, #endif #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "ulimit" built-in. */ int ulimit_builtin(int argc, void **argv) { enum { HARD = 1 << 0, SOFT = 1 << 1, } type = HARD | SOFT; wchar_t resourceoption = L'\0'; const struct resource *resource = &res_fsize; bool print_all = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, ulimit_options, 0)) != NULL) { switch (opt->shortopt) { case L'H': type = HARD; break; case L'S': type = SOFT; break; case L'a': print_all = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: if (opt->ptr != NULL) { resourceoption = opt->shortopt; resource = opt->ptr; break; } else { return Exit_ERROR; } } } assert(type & (HARD | SOFT)); if (print_all) { if (resourceoption != L'\0') return mutually_exclusive_option_error(L'a', resourceoption); if (!validate_operand_count(argc - xoptind, 0, 0)) return Exit_ERROR; return print_all_limits(type & SOFT); } if (!validate_operand_count(argc - xoptind, 0, 1)) return Exit_ERROR; struct rlimit rlimit; if (getrlimit(resource->type, &rlimit) < 0) { xerror(errno, Ngt("cannot get the current limit " "for the resource type of `%s'"), gt(resource->description)); return Exit_FAILURE; } if (xoptind == argc) { print_limit_value(type & SOFT, &rlimit, resource); return yash_error_message_count == 0 ? Exit_SUCCESS : Exit_FAILURE; } /* parse the operand */ rlim_t value; if (wcscmp(ARGV(xoptind), L"hard") == 0) { value = rlimit.rlim_max; } else if (wcscmp(ARGV(xoptind), L"soft") == 0) { value = rlimit.rlim_cur; } else if (wcscmp(ARGV(xoptind), L"unlimited") == 0) { value = RLIM_INFINITY; } else if (iswdigit(ARGV(xoptind)[0])) { unsigned long v; if (!xwcstoul(ARGV(xoptind), 10, &v)) goto err_format; value = (rlim_t) v * resource->factor; if (value / resource->factor != v || value == RLIM_INFINITY || value == RLIM_SAVED_MAX || value == RLIM_SAVED_CUR) { xerror(ERANGE, NULL); return Exit_FAILURE; } } else { goto err_format; } if (type & HARD) rlimit.rlim_max = value; if (type & SOFT) rlimit.rlim_cur = value; /* check if soft limit does not exceed hard limit */ if (rlimit.rlim_max != RLIM_INFINITY && rlimit.rlim_max != RLIM_SAVED_MAX && rlimit.rlim_max != RLIM_SAVED_CUR && (rlimit.rlim_cur == RLIM_INFINITY || (rlimit.rlim_cur != RLIM_SAVED_MAX && rlimit.rlim_cur != RLIM_SAVED_CUR && rlimit.rlim_cur > rlimit.rlim_max))) { xerror(0, Ngt("the soft limit cannot exceed the hard limit")); return Exit_FAILURE; } if (setrlimit(resource->type, &rlimit) < 0) { xerror(errno, Ngt("failed to set the limit")); return Exit_FAILURE; } return Exit_SUCCESS; err_format: xerror(0, Ngt("`%ls' is not a valid integer"), ARGV(xoptind)); return Exit_ERROR; } /* Prints all current ulimit values to the standard output. * Returns the exit status of the ulimit built-in. */ int print_all_limits(bool soft) { const struct xgetopt_T *opt; for (opt = ulimit_options; opt->shortopt != L'\0'; opt++) { const struct resource *resource = opt->ptr; if (resource == NULL) continue; struct rlimit rlimit; if (getrlimit(resource->type, &rlimit) < 0) { xerror(errno, Ngt("cannot get the current limit " "for the resource type of `%s'"), gt(resource->description)); continue; } xprintf(gt("-%lc: %-30s "), (wint_t) opt->shortopt, gt(resource->description)); print_limit_value(soft, &rlimit, resource); } return yash_error_message_count == 0 ? Exit_SUCCESS : Exit_FAILURE; } void print_limit_value(bool soft, const struct rlimit *rlimit, const struct resource *resource) { rlim_t value = soft ? rlimit->rlim_cur : rlimit->rlim_max; if (value == RLIM_INFINITY) xprintf("%s\n", gt("unlimited")); else xprintf("%ju\n", (uintmax_t) (value / resource->factor)); } #if YASH_ENABLE_HELP const char ulimit_help[] = Ngt( "set or print a resource limitation" ); const char ulimit_syntax[] = Ngt( "\tulimit -a [-H|-S]\n" "\tulimit [-H|-S] [-efilnqrstuvx] [limit]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/builtins/ulimit.h000066400000000000000000000021071354143602500157240ustar00rootroot00000000000000/* Yash: yet another shell */ /* ulimit.h: ulimit builtin */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_ULIMIT_H #define YASH_ULIMIT_H #include "../xgetopt.h" extern int ulimit_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char ulimit_help[], ulimit_syntax[]; #endif extern const struct xgetopt_T ulimit_options[]; #endif /* YASH_ULIMIT_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/common.h000066400000000000000000000034121354143602500140600ustar00rootroot00000000000000/* Yash: yet another shell */ /* common.h: defines symbols common to all sources. */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* This file should be included at the very first in every source file. */ #ifndef YASH_COMMON_H #define YASH_COMMON_H #include "config.h" #ifndef __GNUC__ # define __attribute__(ignore) #endif #ifdef __CYGWIN__ # undef __STRICT_ANSI__ # define FD_SETSIZE 256 #endif #if HAVE_GETTEXT # define gt(MSGID) ((const char *) gettext(MSGID)) # define Ngt(MSGID) MSGID #else # define gt(MSGID) MSGID # define Ngt(MSGID) MSGID #endif #if HAVE_NGETTEXT # define ngt(MSGS,MSGP,N) ((const char *) ngettext(MSGS, MSGP, N)) #else # define ngt(MSGS,MSGP,N) gt((N) == 1 ? (MSGS) : (MSGP)) #endif /* This macro suppresses the uninitialized variable warnings from the compiler * by assigning the given dummy value. When debugging is disabled, this macro * just leaves the variable uninitialized. */ #ifdef NDEBUG # define INIT(x, dummy_initial_value) x #else # define INIT(x, dummy_initial_value) x = dummy_initial_value #endif #define ARGV(i) ((wchar_t *) argv[i]) #endif /* YASH_COMMON_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/configure000077500000000000000000001260041354143602500143310ustar00rootroot00000000000000# Manually written configuration script for yash # (C) 2007-2019 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set +Ceu makefile="Makefile" makefilein="Makefile.in" configlog="config.log" configstatus="config.status" configh="config.h" tempsrc=".temp.c" tempo=".temp.o" tempd=".temp.d" tempout="./.temp.out" temptxt="./.temp.txt" dirs=". builtins doc doc/ja lineedit po tests" target="yash" version="2.49" copyright="Copyright (C) 2007-2019 magicant" # object files to be linked as `yash' objs='$(MAIN_OBJS)' # object files compiled into builtins.a builtin_objs='' null="" help="false" nocreate="false" debug="false" enable_array="true" enable_dirstack="true" enable_double_bracket="true" enable_nls="true" enable_help="true" enable_history="true" enable_lineedit="true" enable_printf="true" enable_socket="true" enable_test="true" enable_ulimit="true" default_loadpath='$(yashdatadir)' ctags_args="" etags_args="" intl_lib='intl' term_lib='tinfo curses ncurses ncursesw' prefix='/usr/local' exec_prefix='$(prefix)' bindir='$(exec_prefix)/bin' datarootdir='$(prefix)/share' datadir='$(datarootdir)' localedir='$(datarootdir)/locale' mandir='$(datarootdir)/man' docdir='$(datarootdir)/doc/'"$target" htmldir='$(docdir)' install_program='$(INSTALL) -c' install_data='$(INSTALL) -c -m 644' install_dir='$(INSTALL) -d' unset checkresult unset MAKEFLAGS umask u=rwx quoted0="'"$(printf '%s\n' "$0" | sed -e "s/'/'\\\\''/g")"'" quoted_args= for i do if [ x"$i" != x"--no-create" ] then quoted_args="${quoted_args} '"$(printf '%s\n' "$i" | sed -e "s/'/'\\\\''/g")"'" fi done parseenable() { case "$1" in --enable-*=yes|--enable-*=true) val=true ;; --enable-*=no|--enable-*=false) val=false ;; *=*) echo "$0: $1: invalid option" >&2; exit 2 ;; --enable-*) val=true ;; --disable-*) val=false ;; esac opt="${1#--*able-}" opt="${opt%%=*}" case "$opt" in array) enable_array=$val ;; dirstack) enable_dirstack=$val ;; double-bracket) enable_double_bracket=$val ;; help) enable_help=$val ;; history) enable_history=$val ;; lineedit) enable_lineedit=$val ;; nls) enable_nls=$val ;; printf) enable_printf=$val ;; socket) enable_socket=$val ;; test) enable_test=$val ;; ulimit) enable_ulimit=$val ;; *) echo "$0: $1: invalid option" >&2; exit 2 ;; esac } # parse options for i do case "$i" in -h|--help) help="true" ;; --no-create) nocreate="true" ;; -d|--debug) debug="true" ;; --prefix=*) prefix="${i#--prefix=}" ;; --exec-prefix=*) exec_prefix="${i#--exec-prefix=}" ;; --bindir=*) bindir="${i#--bindir=}" ;; --datarootdir=*) datarootdir="${i#--datarootdir=}" ;; --datadir=*) datadir="${i#--datadir=}" ;; --localedir=*) localedir="${i#--localedir=}" ;; --mandir=*) mandir="${i#--mandir=}" ;; --docdir=*) docdir="${i#--docdir=}" ;; --htmldir=*) htmldir="${i#--htmldir=}" ;; --enable-*|--disable-*) parseenable "$i" ;; --default-loadpath=*) default_loadpath="${i#--default-loadpath=}" ;; --with-intl-lib=*) intl_lib="${i#--with-intl-lib=}" ;; --with-term-lib=*) term_lib="${i#--with-term-lib=}" ;; ?*=*) # parse variable assignment if echo "$i" | grep -E "^[[:alpha:]][[:alnum:]]*=" >/dev/null then export "$i" else echo "$0: $i: unknown argument" >&2 exit 1 fi ;; *) echo "$0: $i: unknown argument" >&2 exit 1 esac done if ${help} then exec cat <"${configlog}" { printf '===== Configuration log: generated by %s\n' "${0##*/}" echo LC_TIME=C date echo printf '# Invocation command line was:\n%%' for i in "$0" "$@" do printf ' %s' "$(printf '%s\n' "$i" | sed -e '\|[^[:alnum:]./=-]| { s/'"'"'/'"'\\''"'/g s/\(.*\)/'"'"'\1'"'"'/ }')" done echo echo printf '# uname -i = %s\n' "$(uname -i 2>/dev/null || echo \?)" printf '# uname -n = %s\n' "$(uname -n 2>/dev/null || echo \?)" printf '# uname -m = %s\n' "$(uname -m 2>/dev/null || echo \?)" printf '# uname -o = %s\n' "$(uname -o 2>/dev/null || echo \?)" printf '# uname -p = %s\n' "$(uname -p 2>/dev/null || echo \?)" printf '# uname -r = %s\n' "$(uname -r 2>/dev/null || echo \?)" printf '# uname -s = %s\n' "$(uname -s 2>/dev/null || echo \?)" printf '# uname -v = %s\n' "$(uname -v 2>/dev/null || echo \?)" printf '# uname -a = %s\n' "$(uname -a 2>/dev/null || echo \?)" echo printf '# PATH=%s\n' "$PATH" echo } >&5 checking () { printf 'checking %s... ' "$1" printf '\nchecking %s...\n' "$1" >&5 } checkby () { printf '%% %s\n' "$*" >&5 "$@" >&5 2>&1 laststatus=$? if [ ${laststatus} -eq 0 ] then checkresult="yes" else printf '# exit status: %d\n' "${laststatus}" >&5 checkresult="no" return ${laststatus} fi } trymake () { checkby ${CC-${cc}} ${CFLAGS-${cflags}} ${CADDS-${null}} \ ${CPPFLAGS-${cppflags}} ${CPPADDS-${null}} \ ${LDFLAGS-${ldflags}} ${LDADDS-${null}} -o "${tempout}" "${tempsrc}" \ "$@" ${LDLIBS-${ldlibs}} } trycompile () { checkby ${CC-${cc}} ${CFLAGS-${cflags}} ${CADDS-${null}} \ ${CPPFLAGS-${cppflags}} ${CPPADDS-${null}} \ -c -o "${tempo}" "${tempsrc}" "$@" } trylink () { checkby ${CC-${cc}} ${LDFLAGS-${ldflags}} ${LDADDS-${null}} \ -o "${tempout}" "${tempo}" "$@" ${LDLIBS-${ldlibs}} } tryexec () { checkby "${tempout}" } checked () { if [ $# -ge 1 ] then checkresult="$1" fi printf '%s\n' "${checkresult}" printf '# result: %s\n' "${checkresult}" >&5 } defconfigh () { printf '# defining %s=%s in %s\n' "$1" "${2-1}" "${configh}" >&5 confighdefs="${confighdefs} #define ${1} ${2-1}" } fail () { echo "configuration failed" >&2 exit 1 } cc="${CC-c99}" cflags="${CFLAGS--O1 -g}" cppflags="${CPPFLAGS-${null}}" ldflags="${LDFLAGS-${null}}" ldlibs="${LDLIBS-${null}}" ar="${AR-ar}" arflags="${ARFLAGS--rc}" xgettext="${XGETTEXT-xgettext}" xgettextflags="${XGETTEXTFLAGS--kgt -kNgt -kngt:1,2 \ --flag=sb_vprintf:1:c-format --flag=sb_printf:1:c-format \ --flag=malloc_vprintf:1:c-format --flag=malloc_printf:1:c-format \ --flag=xerror:1:c-format --flag=serror:1:c-format \ --flag=le_compdebug:1:c-format}" msginit="${MSGINIT-msginit}" msgfmt="${MSGFMT-msgfmt}" msgfmtflags="${MSGFMTFLAGS--c}" msgmerge="${MSGMERGE-msgmerge}" msgmergeflags="${MSGMERGEFLAGS-${null}}" msgconv="${MSGCONV-msgconv}" msgfilter="${MSGFILTER-msgfilter}" asciidoc="${ASCIIDOC-asciidoc}" asciidocflags="${ASCIIDOCFLAGS-}" asciidocattrs="${ASCIIDOCATTRS--a docdate=\"\`date +%Y-%m-%d\`\" \ -a yashversion=\"\$(VERSION)\" -a linkcss -a disable-javascript}" a2x="${A2X-a2x}" a2xflags="${A2XFLAGS-}" ctags="${CTAGS-ctags}" etags="${ETAGS-etags}" cscope="${CSCOPE-cscope}" confighdefs='' # check OS type checking 'operating system' ostype=$(uname -s | tr "[:upper:]" "[:lower:]") checked "${ostype}" case "${ostype}" in darwin) defconfigh "_DARWIN_C_SOURCE" ;; sunos) defconfigh "__EXTENSIONS__" ;; esac # check POSIX conformance checking 'POSIX conformance' posixver=$(getconf _POSIX_VERSION 2>/dev/null) if [ -n "${posixver}" ] && [ x"${posixver}" != x"undefined" ] then checked "${posixver}" else checked "no" posixver="" fi checking 'SUS conformance' xopenver=$(getconf _XOPEN_VERSION 2>/dev/null) if [ -n "${xopenver}" ] && [ x"${xopenver}" != x"undefined" ] then checked "${xopenver}" else checked "no" xopenver="" fi case "${ostype}" in freebsd) # FreeBSD doesn't have a feature test macro to enable non-POSIX # extensions. We don't define _POSIX_C_SOURCE or _XOPEN_SOURCE so that # non-POSIX extensions are available. posix= xopen= ;; *) posix=${posixver} xopen=${xopenver} ;; esac # define options for debugging if ${debug} then cc="${CC-gcc -std=c99}" cflags="${CFLAGS--pedantic -MMD -Wall -Wextra -O1 -fno-inline -ggdb}" else defconfigh "NDEBUG" fi # check if the compiler works checking 'whether the compiler works' cat >"${tempsrc}" <&2 printf 'The compiler is expected to accept these options: %s\n' \ "${CFLAGS-${cflags}} ${CADDS-${null}}" >&2 fail fi # check if the compiler accepts the -O2 option if we are not debugging if ! ${debug} && [ x"${CFLAGS+set}" != x"set" ] then checking 'whether the compiler accepts -O2 flag' savecflags=${cflags} cflags="-O2 -g" cat >"${tempsrc}" <"${temptxt}" </dev/null END checkby eval "${MAKE-make} -f - _TEST_ <|"${tempsrc}" checkby pax -w -x ustar -f "${tempout}" "${tempsrc}" checked if [ x"${checkresult}" = x"yes" ] then archiver='pax -w -x ustar -f' else archiver='tar cf' fi fi # check if the install command is available if [ x"${INSTALL+set}" != x"set" ] then checking 'for the install command' >|"${tempsrc}" checkby install -c -m 644 "${tempsrc}" "${tempout}" checked if [ x"${checkresult}" = x"yes" ] then install='install' else install='$(topdir)/install-sh' fi fi # check if defining _POSIX_C_SOURCE and _XOPEN_SOURCE as values larger than # _POSIX_VERSION and _XOPEN_VERSION is accepted. if [ -n "${posix}" ] then checking 'what values _POSIX_C_SOURCE and _XOPEN_SOURCE should have' if cat >"${tempsrc}" < int main(void) { fork(); return 0; } END trycompile then checked "_POSIX_C_SOURCE=200809L, _XOPEN_SOURCE=700" posix=200809 xopen=700 elif cat >"${tempsrc}" < int main(void) { fork(); return 0; } END trycompile then checked "_POSIX_C_SOURCE=200112L, _XOPEN_SOURCE=600" posix=200112 xopen=600 else checked "failed" fi fi if [ -n "${posix}" ] then defconfigh "_POSIX_C_SOURCE" "${posix}L" fi if [ -n "${xopen}" ] then defconfigh "_XOPEN_SOURCE" "${xopen}" fi # check if we need -lm if [ x"${LDLIBS+set}" != x"set" ] then checking 'if loader flag -lm can be omitted' cat >"${tempsrc}" < int main(int argc, char **argv) { return fmod(argc, 1.1) == trunc(argc) || argv == 0; } END trymake checked if [ x"${checkresult}" = x"no" ] then ldlibs="${ldlibs} -lm" fi fi # enable/disable socket redirection if ${enable_socket} then checking 'for socket library' cat >"${tempsrc}" < #include int main(void) { struct addrinfo ai = { .ai_flags = 0, .ai_family = AF_UNSPEC, .ai_protocol = 0, .ai_socktype = 1 ? SOCK_STREAM : SOCK_DGRAM, .ai_addrlen = 0, .ai_addr = (void*)0, .ai_canonname = (void*)0, .ai_next = (void*)0 }; gai_strerror(getaddrinfo("", "", &ai, (void*)0)); connect(socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol), ai.ai_addr, ai.ai_addrlen); freeaddrinfo(&ai); } END saveldlibs="${ldlibs}" if trymake then checked "yes" else for lib in '-lxnet' '-lsocket' '-lnsl' '-lsocket -lnsl' do ldlibs="${saveldlibs} ${lib}" if trymake then checked "with ${lib}" break fi done fi case "${checkresult}" in yes|with*) defconfigh "YASH_ENABLE_SOCKET" unset saveldlibs ;; no) checked "no" printf 'The socket library is unavailable!\n' >&2 printf 'Add the "--disable-socket" option and try again.\n' >&2 fail ;; esac fi # check if gettext is available if ${enable_nls} then checking 'for ngettext' cat >"${tempsrc}" < int main(void) { const char *s = ngettext("foo", "bar", 1); return s == 0; } END trycompile checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_NGETTEXT" fi checking 'for gettext library' cat >"${tempsrc}" < int main(void) { bindtextdomain("", ""); textdomain(""); const char *s = gettext(""); #if HAVE_NGETTEXT s = ngettext("", "", 0); #endif return s == 0; } END if trycompile then saveldlibs="${ldlibs}" if trylink then checked "yes" else for lib in ${intl_lib} do ldlibs="${saveldlibs} -l${lib}" if trylink then checked "with -l${lib}" break fi done fi fi case "${checkresult}" in yes|with*) defconfigh "HAVE_GETTEXT" unset saveldlibs ;; no) checked "no" printf 'The gettext library is unavailable!\n' >&2 printf 'Add the "--disable-nls" option and try again.\n' >&2 fail ;; esac fi # check if terminfo is available if ${enable_lineedit} then for i in curses.h:CURSES_H ncurses.h:NCURSES_H \ ncurses/ncurses.h:NCURSES_NCURSES_H \ ncursesw/ncurses.h:NCURSESW_NCURSES_H do checking "for ${i%:*}" cat >"${tempsrc}" < int main(void) { return ERR; } END trycompile checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_${i#*:}" break fi done for i in term.h:TERM_H ncurses/term.h:NCURSES_TERM_H \ ncursesw/term.h:NCURSESW_TERM_H do checking "for ${i%:*}" cat >"${tempsrc}" < #elif HAVE_NCURSES_H #include #elif HAVE_NCURSES_NCURSES_H #include #elif HAVE_NCURSESW_NCURSES_H #include #endif #include <${i%:*}> int main(void) { int (*f1)(char *, int, int *) = setupterm; f1("", 0, 0); int (*f2)(char *) = tigetflag; f2(""); int (*f3)(char *) = tigetnum; f3(""); char *(*f4)(char *) = tigetstr; f4(""); int (*f5)(const char *, int, int (*)(int)) = tputs; f5("", 0, 0); /* char *(*f6)(char *, long,long,long,long,long,long,long,long,long) = tparm; f6("", 0, 0, 0, 0, 0, 0, 0, 0, 0); */ int (*f7)(TERMINAL *) = del_curterm; f7(cur_term); } END trycompile checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_${i#*:}" break fi done checking 'for terminfo library' cat >"${tempsrc}" < #if HAVE_CURSES_H #include #elif HAVE_NCURSES_H #include #elif HAVE_NCURSES_NCURSES_H #include #elif HAVE_NCURSESW_NCURSES_H #include #endif #if HAVE_TERM_H #include #elif HAVE_NCURSES_TERM_H #include #elif HAVE_NCURSESW_TERM_H #include #endif int main(void) { int i1 = setupterm((char*)0, 1, (int*)0); int i2 = tigetflag(""); int i3 = tigetnum(""); char *s1 = tigetstr(""); char *s2 = tparm(s1, (long) i1, (long) i2, (long) i3, 0L, 0L, 0L, 0L, 0L, 0L); int i4 = tputs(s2, 1, putchar); del_curterm(cur_term); return i4; } END if trycompile then saveldlibs="${ldlibs}" if trylink then checked "yes" else for lib in ${term_lib} do ldlibs="${saveldlibs} -l${lib}" if trylink then checked "with -l${lib}" break fi done fi fi case "${checkresult}" in yes|with*) defconfigh "YASH_ENABLE_LINEEDIT" unset saveldlibs ;; no) checked "no" printf 'The terminfo (curses) library is unavailable!\n' >&2 printf 'Add the "--disable-lineedit" option and try again.\n' >&2 fail ;; esac fi # check whether system has /proc/self/exe or similar utility file if checking 'for /proc/self/exe' checkby /proc/self/exe -c true checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_PROC_SELF_EXE" elif checking 'for /proc/curproc/file' checkby /proc/curproc/file -c true checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_PROC_CURPROC_FILE" elif checking 'for /proc/$$/object/a.out' checkby eval '/proc/$$/object/a.out -c true' checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_PROC_OBJECT_AOUT" fi # check for strnlen checking 'for strnlen' cat >"${tempsrc}" < #ifndef strnlen size_t strnlen(const char*, size_t); #endif int main(void) { return strnlen("12345", 3) != 3; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_STRNLEN" fi # check for strdup checking 'for strdup' cat >"${tempsrc}" < #ifndef strdup char *strdup(const char*); #endif int main(void) { char *dup = strdup("12345"); if (!dup) return 1; int cmp = strcmp(dup, "12345"); free(dup); return cmp != 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_STRDUP" fi # check for wcsnlen checking 'for wcsnlen' cat >"${tempsrc}" < #ifndef wcsnlen size_t wcsnlen(const wchar_t*, size_t); #endif int main(void) { return wcsnlen(L"12345", 3) != 3; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCSNLEN" fi # check for wcsdup checking 'for wcsdup' cat >"${tempsrc}" < #ifndef wcsdup wchar_t *wcsdup(const wchar_t*); #endif int main(void) { char *dup = wcsdup("12345"); if (!dup) return 1; int cmp = wcscmp(dup, "12345"); free(dup); return cmp != 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCSDUP" fi # check for wcscasecmp checking 'for wcscasecmp' cat >"${tempsrc}" < #ifndef wcscasecmp int wcscasecmp(const wchar_t*, const wchar_t*); #endif int main(void) { return wcscasecmp(L"1a2b3c", L"1A2B3C") != 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCSCASECMP" fi # check for wcsnrtombs checking 'for wcsnrtombs' cat >"${tempsrc}" < #include #include #ifndef wcsnrtombs size_t wcsnrtombs(char *restrict, const wchar_t **restrict, size_t, size_t, mbstate_t *restrict); #endif int main(void) { mbstate_t s; char out[10]; const wchar_t w[] = L"abcde"; const wchar_t *in = w; memset(&s, 0, sizeof s); return wcsnrtombs(out, &in, SIZE_MAX, sizeof out, &s) != 5 || in != NULL || strcmp(out, "abcde") != 0 || wcsnrtombs(out, (in = &w[1], &in), 3, sizeof out, &s) != 3 || in != &w[4] || strncmp(out, "bcd", 3) != 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCSNRTOMBS" fi # check for wcstold checking 'for wcstold' cat >"${tempsrc}" < #ifndef wcstold long double wcstold(const wchar_t *restrict, wchar_t **restrict); #endif int main(void) { return wcstold(L"10.0", (wchar_t **) 0) != 10.0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCSTOLD" fi # check for wcwidth checking 'for wcwidth' cat >"${tempsrc}" < #ifndef wcwidth int wcwidth(wchar_t); #endif int main(void) { return wcwidth(L'\0'); } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCWIDTH" fi # check if sys/stat.h defines S_ISVTX checking 'if defines S_ISVTX' cat >"${tempsrc}" < int main(void) { return S_ISVTX & 0; } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_S_ISVTX" fi # check if unsetenv returns int checking 'if unsetenv returns int' cat >"${tempsrc}" < int main(void) { return 0 != unsetenv("x"); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "UNSETENV_RETURNS_INT" fi # check if the "st_atim"/"st_atimespec"/"st_atimensec"/"__st_atimensec" member # of the "stat" structure is available if ${enable_test} then if checking 'for st_atim' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_atim = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 }; struct timespec ts = st.st_atim; return ts.tv_nsec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_ATIM" elif checking 'for st_atimespec' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_atimespec = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 }; struct timespec ts = st.st_atimespec; return ts.tv_nsec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_ATIMESPEC" elif checking 'for st_atimensec' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_atimensec = 0; return st.st_atimensec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_ATIMENSEC" elif checking 'for __st_atimensec' cat >"${tempsrc}" < int main(void) { struct stat st; st.__st_atimensec = 0; return st.__st_atimensec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE___ST_ATIMENSEC" fi fi # check if the "st_mtim"/"st_mtimespec"/"st_mtimensec"/"__st_mtimensec" member # of the "stat" structure is available if checking 'for st_mtim' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_mtim = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 }; struct timespec ts = st.st_mtim; return ts.tv_nsec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_MTIM" elif checking 'for st_mtimespec' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_mtimespec = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 }; struct timespec ts = st.st_mtimespec; return ts.tv_nsec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_MTIMESPEC" elif checking 'for st_mtimensec' cat >"${tempsrc}" < int main(void) { struct stat st; st.st_mtimensec = 0; return st.st_mtimensec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_ST_MTIMENSEC" elif checking 'for __st_mtimensec' cat >"${tempsrc}" < int main(void) { struct stat st; st.__st_mtimensec = 0; return st.__st_mtimensec != 0; } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE___ST_MTIMENSEC" fi # check if WCONTINUED and WIFCONTINUED are available checking 'for WCONTINUED and WIFCONTINUED' cat >"${tempsrc}" < #include static int s; int main(void) { if (WIFCONTINUED(s)) { } return waitpid(-1, &s, WNOHANG|WCONTINUED) < 0 && errno == EINVAL; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_WCONTINUED" fi # check for faccessat/eaccess if checking 'for faccessat' cat >"${tempsrc}" < #include #ifndef faccessat extern int faccessat(int, const char *, int, int); #endif int main(void) { faccessat(AT_FDCWD, ".", F_OK | R_OK | W_OK | X_OK, AT_EACCESS); } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_FACCESSAT" elif checking 'for eaccess' cat >"${tempsrc}" < #ifndef eaccess extern int eaccess(const char *, int); #endif int main(void) { eaccess(".", F_OK | R_OK | W_OK | X_OK); } END trymake checked [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_EACCESS" fi # check for strsignal checking 'for strsingal' cat >"${tempsrc}" < #include #ifndef strsignal char *strsignal(int); #endif int main(void) { (void) strsignal(SIGKILL); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_STRSIGNAL" fi # check for setpwent & getpwent & endpwent checking 'for setpwent/getpwent/endpwent' cat >"${tempsrc}" < #ifndef setpwent void setpwent(void); #endif #ifndef getpwent struct passwd *getpwent(void); #endif #ifndef endpwent void endpwent(void); #endif struct passwd *p; int main(void) { setpwent(); p = getpwent(); endpwent(); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_GETPWENT" # check for pw_gecos checking 'for pw_gecos' cat >"${tempsrc}" < const char *s; struct passwd pwd; int main(void) { pwd.pw_gecos = ""; s = pwd.pw_gecos; } END trycompile checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_PW_GECOS" fi fi # check for setgrent & getgrent & endgrent checking 'for setgrent/getgrent/endgrent' cat >"${tempsrc}" < /* don't declare setgrent to avoid conflict on BSD */ #ifndef getgrent struct group *getgrent(void); #endif #ifndef endgrent void endgrent(void); #endif struct group *g; int main(void) { setgrent(); g = getgrent(); endgrent(); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_GETGRENT" fi # check for sethostent & gethostent & endhostent # This check may fail if socket redirection is disabled # because the -lxnet compiler option is not specified. checking 'for sethostent/gethostent/endhostent' cat >"${tempsrc}" < /* don't declare sethostent and endhostent to avoid conflict on SunOS */ #ifndef gethostent struct hostent *gethostent(void); #endif struct hostent *h; int main(void) { sethostent(1); h = gethostent(); endhostent(); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_GETHOSTENT" fi # check for paths.h checking 'for paths.h' cat >"${tempsrc}" < #include int main(void) { printf("%s\n", _PATH_BSHELL); } END trycompile checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_PATHS_H" fi # check if getcwd accepts (NULL,0) as argument checking "if getcwd(NULL,0) returns malloc'ed string" cat >"${tempsrc}" < #include #include #include char *xgetcwd(void) { size_t pwdlen = 80; char *pwd = malloc(pwdlen); if (!pwd) return NULL; while (getcwd(pwd, pwdlen) == NULL) { if (errno == ERANGE) { pwdlen *= 2; pwd = realloc(pwd, pwdlen); if (!pwd) return NULL; } else { free(pwd); return NULL; } } return pwd; } int main(void) { char *wd1 = getcwd(NULL, 0); if (!wd1) return 1; char *wd2 = xgetcwd(); if (!wd2 || strcmp(wd1, wd2) != 0) return 1; char *wd11 = realloc(wd1, strlen(wd1) + 10); if (!wd11 || strcmp(wd11, wd2) != 0) return 1; free(wd11); free(wd2); return 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "GETCWD_AUTO_MALLOC" fi # check if ioctl supports TIOCGWINSZ if ${enable_lineedit} then checking 'if ioctl supports TIOCGWINSZ' cat >"${tempsrc}" < int main(void) { struct winsize ws; ioctl(0, TIOCGWINSZ, &ws); (void) ws.ws_row, (void) ws.ws_col; } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_TIOCGWINSZ" fi fi # check if wide-oriented I/O is working checking 'if wide-oriented I/O is fully working' cat >"${tempsrc}" < #include #define LEN 12345 int main(void) { FILE *f; fpos_t pos; f = fopen("${tempsrc}", "w+"); if (f == NULL) return 1; if (fwprintf(f, L"123\n") != 4) return 2; if (fseek(f, 0, SEEK_SET) != 0) return 3; if (fgetwc(f) != L'1') return 4; if (fgetwc(f) != L'2') return 5; if (fgetpos(f, &pos) != 0) return 6; if (fseek(f, 0, SEEK_SET) != 0) return 7; if (fgetwc(f) != L'1') return 8; if (fsetpos(f, &pos) != 0) return 9; if (fgetwc(f) != L'3') return 10; if (fseek(f, 0, SEEK_SET) != 0) return 11; for (size_t i = 0; i < LEN; i++) if (fputwc(L'0', f) != L'0') return 12; if (fgetpos(f, &pos) != 0) return 13; if (fputwc(L'1', f) != L'1') return 14; if (fseek(f, 0, SEEK_SET) != 0) return 15; if (fgetwc(f) != L'0') return 16; if (fsetpos(f, &pos) != 0) return 17; if (fgetwc(f) != L'1') return 18; if (fseek(f, 0, SEEK_SET) != 0) return 19; for (size_t i = 0; i < LEN; i++) if (fgetwc(f) != L'0') return 20; if (fgetwc(f) != L'1') return 21; if (fclose(f) != 0) return 22; return 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"no" ] then defconfigh "WIO_BROKEN" fi # check if fgetws is working checking 'if fgetws is fully working' cat >"${tempsrc}" < #include int main(void) { wchar_t buf[100]; FILE *f = fopen("${tempsrc}", "w+"); if (f == NULL) return 1; if (fwprintf(f, L"123\n") != 4) return 2; if (fseek(f, 0, SEEK_SET) != 0) return 3; if (fgetws(buf, 2, f) == NULL) return 4; if (wcscmp(buf, L"1") != 0) return 5; if (fclose(f) != 0) return 6; return 0; } END trymake && tryexec checked if [ x"${checkresult}" = x"no" ] then defconfigh "FGETWS_BROKEN" fi echo >&5 # enable/disable the array builtin if ${enable_array} then defconfigh "YASH_ENABLE_ARRAY" fi # enable/disable the double-bracket command if ${enable_double_bracket} then defconfigh "YASH_ENABLE_DOUBLE_BRACKET" fi # enable/disable the directory stack if ${enable_dirstack} then defconfigh "YASH_ENABLE_DIRSTACK" fi # enable/disable the help builtin if ${enable_help} then defconfigh "YASH_ENABLE_HELP" fi # enable/disable history if ${enable_history} then defconfigh "YASH_ENABLE_HISTORY" objs="$objs "'$(HISTORY_OBJS)' else if ${enable_lineedit} then printf 'History is required for lineedit but disabled.\n' >&2 printf 'Add the "--disable-lineedit" option and try again.\n' >&2 fail fi fi # enable/disable the echo/printf builtins if ${enable_printf} then defconfigh "YASH_ENABLE_PRINTF" builtin_objs="$builtin_objs "'$(PRINTF_OBJS)' fi # enable/disable the test builtin if ${enable_test} then defconfigh "YASH_ENABLE_TEST" builtin_objs="$builtin_objs "'$(TEST_OBJS)' else if ${enable_double_bracket} then cat >&2 <"${tempsrc}" < /* required before on freebsd */ #include /* required before on old Mac OS X */ #include int main(void) { struct rlimit l = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY }; getrlimit(RLIMIT_FSIZE, &l); setrlimit(RLIMIT_FSIZE, &l); } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "YASH_ENABLE_ULIMIT" builtin_objs="$builtin_objs "'$(ULIMIT_OBJS)' else printf 'The getrlimit and setrlimit functions are unavailable.\n' >&2 printf 'Add the "--disable-ulimit" option and try again.\n' >&2 fail fi # check for RLIM_SAVED_CUR & RLIM_SAVED_MAX for i in CUR MAX do checking "for RLIM_SAVED_${i}" cat >"${tempsrc}" < /* required before on freebsd */ #include /* required before on old Mac OS X */ #include int main(void) { struct rlimit l = { .rlim_cur = RLIM_SAVED_${i}, .rlim_max = RLIM_SAVED_${i} }; return l.rlim_cur == l.rlim_max; } END trymake checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_RLIM_SAVED_${i}" fi done # check for RLIMIT_*** for i in AS LOCKS MEMLOCK MSGQUEUE NICE NPROC RSS RTPRIO SIGPENDING do checking "for RLIMIT_${i}" cat >"${tempsrc}" < /* required before on freebsd */ #include /* required before on old Mac OS X */ #include #if HAVE_RLIMIT_AS int main(void) { return RLIMIT_${i} == RLIMIT_AS; getrlimit(RLIMIT_${i}, 0); } #else int main(void) { return 0; getrlimit(RLIMIT_${i}, 0); } #endif END trymake && tryexec checked if [ x"${checkresult}" = x"yes" ] then defconfigh "HAVE_RLIMIT_${i}" fi done fi # check if ctags/etags accepts the --recurse option if [ x"${CTAGSARGS+set}" != x"set" ] then checking "if ctags accepts --recurse option" rm -fr "${temptxt}" printf 'int f(int){}\n' >|"${tempsrc}" checkby ${ctags} --recurse -f "${temptxt}" "${tempsrc}" checked if [ x"${checkresult}" = x"yes" ] then CTAGSARGS='--recurse' else CTAGSARGS='*.[ch]' fi fi if [ x"${ETAGSARGS+set}" != x"set" ] then checking "if etags accepts --recurse option" rm -fr "${temptxt}" printf 'int f(int){}\n' >|"${tempsrc}" checkby ${etags} --recurse -o "${temptxt}" "${tempsrc}" checked if [ x"${checkresult}" = x"yes" ] then ETAGSARGS='--recurse' else ETAGSARGS='*.[ch]' fi fi # set cscope invocation parameter : ${CSCOPEARGS=-bR} if [ -n "${builtin_objs}" ] then objs="${objs} "'$(BUILTINS_ARCHIVE)' fi if ${enable_lineedit} then objs="${objs} "'$(LINEEDIT_ARCHIVE)' fi if [ x"${LINGUAS+set}" = x"set" ] then CATALOGS='' for i in ${LINGUAS} do CATALOGS="${CATALOGS} ${i}.mo" done else CATALOGS='$(MOFILES)' fi MAKE_INCLUDE="${MAKE_INCLUDE-${make_include}}" MAKE_SHELL="${MAKE_SHELL-${make_shell}}" CC="${CC-${cc}}" CFLAGS="${CFLAGS-${cflags}}${CADDS:+ ${CADDS}}" CPPFLAGS="${CPPFLAGS-${cppflags}}${CPPADDS:+ ${CPPADDS}}" LDFLAGS="${LDFLAGS-${ldflags}}${LDADDS:+ ${LDADDS}}" LDLIBS="${LDLIBS-${ldlibs}}" AR="${AR-${ar}}" ARFLAGS="${ARFLAGS-${arflags}}" ARCHIVER="${ARCHIVER-${archiver}}" XGETTEXT="${XGETTEXT-${xgettext}}" XGETTEXTFLAGS="${XGETTEXTFLAGS-${xgettextflags}}" MSGINIT="${MSGINIT-${msginit}}" MSGFMT="${MSGFMT-${msgfmt}}" MSGFMTFLAGS="${MSGFMTFLAGS-${msgfmtflags}}" MSGMERGE="${MSGMERGE-${msgmerge}}" MSGMERGEFLAGS="${MSGMERGEFLAGS-${msgmergeflags}}" MSGCONV="${MSGCONV-${msgconv}}" MSGFILTER="${MSGFILTER-${msgfilter}}" ASCIIDOC="${ASCIIDOC-${asciidoc}}" ASCIIDOCFLAGS="${ASCIIDOCFLAGS-${asciidocflags}}" ASCIIDOCATTRS="${ASCIIDOCATTRS-${asciidocattrs}}" A2X="${A2X-${a2x}}" A2XFLAGS="${A2XFLAGS-${a2xflags}}" CTAGS="${CTAGS-${ctags}}" ETAGS="${ETAGS-${etags}}" CSCOPE="${CSCOPE-${cscope}}" DIRS="${dirs}" OBJS="${objs}" BUILTIN_OBJS="${builtin_objs}" TARGET="${target}" VERSION="${version}" COPYRIGHT="${copyright}" INSTALL="${INSTALL-${install}}" INSTALL_PROGRAM="${INSTALL_PROGRAM-${install_program}}" INSTALL_DATA="${INSTALL_DATA-${install_data}}" INSTALL_DIR="${INSTALL_DIR-${install_dir}}" case "${prefix}" in / ) prefix= ;; //) prefix=/ ;; esac case "${exec_prefix}" in / ) exec_prefix= ;; //) exec_prefix=/ ;; esac case "${bindir}" in / ) bindir= ;; //) bindir=/ ;; esac case "${datarootdir}" in / ) datarootdir= ;; //) datarootdir=/ ;; esac case "${datadir}" in / ) datadir= ;; //) datadir=/ ;; esac case "${localedir}" in / ) localedir= ;; //) localedir=/ ;; esac case "${mandir}" in / ) mandir= ;; //) mandir=/ ;; esac case "${docdir}" in / ) docdir= ;; //) docdir=/ ;; esac case "${htmldir}" in / ) htmldir= ;; //) htmldir=/ ;; esac sed_subst_cmd=" s!@MAKE_INCLUDE@!${MAKE_INCLUDE}!g s!@MAKE_SHELL@!${MAKE_SHELL}!g s!@CC@!${CC}!g s!@CFLAGS@!${CFLAGS}!g s!@CPPFLAGS@!${CPPFLAGS}!g s!@LDFLAGS@!${LDFLAGS}!g s!@LDLIBS@!${LDLIBS}!g s!@AR@!${AR}!g s!@ARFLAGS@!${ARFLAGS}!g s!@ARCHIVER@!${ARCHIVER}!g s!@XGETTEXT@!${XGETTEXT}!g s!@XGETTEXTFLAGS@!${XGETTEXTFLAGS}!g s!@MSGINIT@!${MSGINIT}!g s!@MSGFMT@!${MSGFMT}!g s!@MSGFMTFLAGS@!${MSGFMTFLAGS}!g s!@MSGMERGE@!${MSGMERGE}!g s!@MSGMERGEFLAGS@!${MSGMERGEFLAGS}!g s!@MSGCONV@!${MSGCONV}!g s!@MSGFILTER@!${MSGFILTER}!g s!@ASCIIDOC@!${ASCIIDOC}!g s!@ASCIIDOCFLAGS@!${ASCIIDOCFLAGS}!g s!@ASCIIDOCATTRS@!${ASCIIDOCATTRS}!g s!@A2X@!${A2X}!g s!@A2XFLAGS@!${A2XFLAGS}!g s!@CATALOGS@!${CATALOGS}!g s!@CTAGS@!${CTAGS}!g s!@CTAGSARGS@!${CTAGSARGS}!g s!@ETAGS@!${ETAGS}!g s!@ETAGSARGS@!${ETAGSARGS}!g s!@CSCOPE@!${CSCOPE}!g s!@CSCOPEARGS@!${CSCOPEARGS}!g s!@DIRS@!${DIRS}!g s!@OBJS@!${OBJS}!g s!@BUILTIN_OBJS@!${BUILTIN_OBJS}!g s!@TARGET@!${TARGET}!g s!@VERSION@!${VERSION}!g s!@COPYRIGHT@!${COPYRIGHT}!g s!@INSTALL@!${INSTALL}!g s!@INSTALL_PROGRAM@!${INSTALL_PROGRAM}!g s!@INSTALL_DATA@!${INSTALL_DATA}!g s!@INSTALL_DIR@!${INSTALL_DIR}!g s!@prefix@!${prefix}!g s!@exec_prefix@!${exec_prefix}!g s!@bindir@!${bindir}!g s!@datarootdir@!${datarootdir}!g s!@datadir@!${datadir}!g s!@localedir@!${localedir}!g s!@mandir@!${mandir}!g s!@docdir@!${docdir}!g s!@htmldir@!${htmldir}!g s!@default_loadpath@!${default_loadpath}!g s!@enable_nls@!${enable_nls}!g " printf '\n\n===== Output variables =====\n%s\n' "${sed_subst_cmd}" >&5 # create dependency files if there are none makedep() { for i in ${1}*.c do if [ -f "${i}" ] && [ ! -f "${i%.c}.d" ] then printf 'touching %s\n' "${i%.c}.d" >>"${i%.c}.d" printf 'Touched %s\n' "${i%.c}.d" >&5 fi done for i in ${1}*/ do if [ -d "${i}" ] then makedep "${i}" fi done } makedep '' # create config.status printf 'creating %s... ' "${configstatus}" cat >"${configstatus}" <&2 exit 1 ;; *) break ;; esac shift done if \$help then exec cat <'${configh}' <"\${target}" if ! ${make_supports_include} && ls "\${dir}"/*.d >/dev/null 2>&1 then printf '(including dependencies) ' cat "\${dir}"/*.d >>"\${target}" fi ;; esac printf 'done\n' done CONFIG_STATUS_END chmod a+x "${configstatus}" printf 'done\n' printf 'Created %s\n' "${configstatus}" >&5 if ! ${nocreate} then printf '%% %s\n' "${CONFIG_SHELL-sh} ${configstatus}" >&5 ${CONFIG_SHELL-sh} ${configstatus} fi # print warning if POSIX conformance is missing if [ -z "${posixver}" ] || [ "${posixver}" -lt 200112 ] then echo "WARNING: yash is designed for systems that conform to POSIX.1-2001" echo " but your system does not" fi # vim: set ft=sh ts=8 sts=4 sw=4 noet tw=80: yash-2.49/debug/000077500000000000000000000000001354143602500135055ustar00rootroot00000000000000yash-2.49/debug/yashrc.debug000066400000000000000000000006721354143602500160130ustar00rootroot00000000000000workdir=$(dirname -- "$0") YASH_LOADPATH=("${workdir}/share") PS1='\fy.[debug] \$$$ -$- j\j h\! ?$?\n'"$PS1" PS1S='\fg.' PS2='\fy.'"$PS2" PS2S='\fg.' trap 'echo SIGUSR1 received!!!' USR1 FCEDIT=vim HISTFILE=/tmp/yash-debug-history-$LOGNAME HISTSIZE=15 HISTRMDUP=3 CDPATH=.:~:/usr/ p() if [ $# -gt 0 ]; then printf '%s\n' "$@" fi set -abefuC --nocaseglob --dotglob --histspace --markdirs --nullglob # vim: set et sw=2 sts=2 tw=78 ft=sh: yash-2.49/debug/yashrc.sample000066400000000000000000000002101354143602500161720ustar00rootroot00000000000000workdir=$(dirname -- "$0") YASH_LOADPATH=("${workdir}/share") . --autoload initialization/sample # vim: set et sw=2 sts=2 tw=78 ft=sh: yash-2.49/doc/000077500000000000000000000000001354143602500131645ustar00rootroot00000000000000yash-2.49/doc/Makefile.in000066400000000000000000000130521354143602500152320ustar00rootroot00000000000000# Makefile.in for the documentation of yash # (C) 2007-2018 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # NOTE: In this Makefile it is assumed that the make implementation allows the # use of hyphens in target names. This means that there may be a strictly # POSIX-conforming implementation of make that rejects this Makefile. I have # never seen such an implementation but if you know of one please let me know. .POSIX: .SUFFIXES: .txt .1 .xml .html @MAKE_SHELL@ topdir = .. subdir = doc recdirs = ja ASCIIDOC = @ASCIIDOC@ ASCIIDOCFLAGS = @ASCIIDOCFLAGS@ ASCIIDOCATTRS = @ASCIIDOCATTRS@ A2X = @A2X@ A2XFLAGS = @A2XFLAGS@ INSTALL = @INSTALL@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_DIR = @INSTALL_DIR@ VERSION = @VERSION@ MANTXT = yash.txt INDEXTXT = index.txt # MAINTXTS must be in the contents order MAINTXTS = intro.txt invoke.txt syntax.txt params.txt expand.txt pattern.txt redir.txt exec.txt interact.txt job.txt builtin.txt lineedit.txt posix.txt faq.txt fgrammar.txt # BUILTINTXTS must be in the alphabetic order BUILTINTXTS = _alias.txt _array.txt _bg.txt _bindkey.txt _break.txt _cd.txt _colon.txt _command.txt _complete.txt _continue.txt _dirs.txt _disown.txt _dot.txt _echo.txt _eval.txt _exec.txt _exit.txt _export.txt _false.txt _fc.txt _fg.txt _getopts.txt _hash.txt _help.txt _history.txt _jobs.txt _kill.txt _local.txt _popd.txt _printf.txt _pushd.txt _pwd.txt _read.txt _readonly.txt _return.txt _set.txt _shift.txt _suspend.txt _test.txt _times.txt _trap.txt _true.txt _type.txt _typeset.txt _ulimit.txt _umask.txt _unalias.txt _unset.txt _wait.txt # CONTENTSTXTS must be in the contents order CONTENTSTXTS = $(MAINTXTS) $(BUILTINTXTS) TXTS = $(MANTXT) $(INDEXTXT) $(CONTENTSTXTS) MANFILE = $(MANTXT:.txt=.1) INDEXHTML = $(INDEXTXT:.txt=.html) HTMLFILES = $(INDEXHTML) $(CONTENTSTXTS:.txt=.html) HTMLAUXFILES = asciidoc.css TARGETS = $(MANFILE) $(HTMLFILES) CONFFILE = asciidoc.conf DESTDIR = prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datarootdir = @datarootdir@ datadir = @datadir@ yashdatadir = $(datadir)/$(TARGET) localedir = @localedir@ mandir = @mandir@ man1dir = $(mandir)/man1 docdir = @docdir@ htmldir = @htmldir@ default-rec: default all-rec: all man-rec: man html-rec: html default: man html all: $(TARGETS) man: $(MANFILE) html: $(HTMLFILES) .txt.1: $(A2X) -d manpage -f manpage -L $(A2XFLAGS) $(ASCIIDOCATTRS) $< .txt.html: $(ASCIIDOC) -b html -d article $(ASCIIDOCFLAGS) $(ASCIIDOCATTRS) $< $(MANTXT): makeyash.sh $(MANTXT).in Makefile.in $(SHELL) makeyash.sh $(CONTENTSTXTS) <$@.in >$@ $(INDEXTXT): makeindex.sh $(INDEXTXT).in $(MAINTXTS) Makefile.in $(SHELL) makeindex.sh $(MAINTXTS) <$@.in >$@ $(MANFILE): $(MANTXT) $(CONTENTSTXTS) $(CONFFILE) $(HTMLFILES): $(CONFFILE) # update date in index.html when any contents file has been modified $(INDEXHTML): $(CONTENTSTXTS) install-rec: install install-data-rec: install-data install-html-rec: install-html installdirs-rec: installdirs installdirs-data-rec: installdirs-data installdirs-html-rec: installdirs-html uninstall-rec: uninstall uninstall-data-rec: uninstall-data INSTALLDIRS = $(DESTDIR)$(man1dir) $(DESTDIR)$(htmldir) install: install-data install-data: man installdirs-data $(INSTALL_DATA) $(MANFILE) $(DESTDIR)$(man1dir) install-html: html installdirs-html $(INSTALL_DATA) $(HTMLFILES) $(HTMLAUXFILES) $(DESTDIR)$(htmldir) installdirs: installdirs-data installdirs-data: $(DESTDIR)$(man1dir) installdirs-html: $(DESTDIR)$(htmldir) $(INSTALLDIRS): $(INSTALL_DIR) $@ uninstall: uninstall-data uninstall-data: rm -f $(DESTDIR)$(man1dir)/$(MANFILE) (if cd $(DESTDIR)$(htmldir); then rm -f $(HTMLFILES) $(HTMLAUXFILES); fi) -rmdir $(DESTDIR)$(htmldir) DISTFILES = $(TARGETS) $(HTMLAUXFILES) $(TXTS) $(MANTXT).in $(INDEXTXT).in makeyash.sh makeindex.sh Makefile.in $(CONFFILE) distfiles: $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(topdir)/$(DISTTARGETDIR) mostlyclean: _mostlyclean _mostlyclean: clean: _clean _clean: _mostlyclean distclean: _distclean _distclean: _clean rm -fr Makefile maintainer-clean: _maintainer-clean _maintainer-clean: _distclean rm -fr $(TARGETS) $(MANTXT) $(INDEXTXT) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) default-rec all-rec man-rec html-rec install-rec install-data-rec install-html-rec installdirs-rec installdirs-data-rec installdirs-html-rec uninstall-rec uninstall-data-rec mostlyclean clean distclean maintainer-clean: @+for dir in $(recdirs); do (cd $$dir && $(MAKE) $@) done .PHONY: default-rec all-rec man-rec html-rec default all man html install-rec install-data-rec install-html-rec installdirs-rec installdirs-data-rec installdirs-html-rec uninstall-rec uninstall-data-rec install install-data install-html installdirs installdirs-data installdirs-html uninstall uninstall-data distfiles copy-distfiles mostlyclean _mostlyclean clean _clean distclean _distclean maintainer-clean _maintainer-clean yash-2.49/doc/_alias.txt000066400000000000000000000027741354143602500151670ustar00rootroot00000000000000= Alias built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Alias built-in The dfn:[alias built-in] defines and/or prints link:syntax.html#aliases[aliases]. [[syntax]] == Syntax - +alias [-gp] [{{name}}[={{value}}]...]+ [[description]] == Description The alias built-in defines and/or prints link:syntax.html#aliases[aliases] as specified by operands. The printed aliases can be used as (part of) shell commands. The built-in prints all currently defined aliases when given no operands. [[options]] == Options +-g+:: +--global+:: With this option, aliases are defined as global aliases; without this option, as normal aliases. +-p+:: +--prefix+:: With this option, aliases are printed in a full command form like `alias -g foo='bar'`. Without this option, only command operands are printed like `foo='bar'`. [[operands]] == Operands {{name}}:: The name of an alias that should be printed. {{name}}={{value}}:: The name and value of an alias that is being defined. [[exitstatus]] == Exit status The exit status of the alias built-in is zero unless there is any error. [[notes]] == Notes The characters that cannot be used in an alias name are the space, tab, newline, and any of +=$<>\'"`;&|()#+. You can use any characters in an alias value. The alias built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard defines no options for the alias built-in, thus no options are available in the link:posix.html[POSIXly correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_array.txt000066400000000000000000000043201354143602500152010ustar00rootroot00000000000000= Array built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Array built-in The dfn:[array built-in] prints or modifies link:params.html#arrays[arrays]. [[syntax]] == Syntax - +array+ - +array {{name}} [{{value}}...]+ - +array -d {{name}} [{{index}}...]+ - +array -i {{name}} {{index}} [{{value}}...]+ - +array -s {{name}} {{index}} {{value}}+ [[description]] == Description When executed without any option or operands, the built-in prints all array definitions to the standard output in a form that can be parsed as commands. When executed with {{name}} and {{value}}s (but without an option), the built-in sets the {{value}}s as the values of the array named {{name}}. With the +-d+ (+--delete+) option, the built-in removes the {{index}}th values of the array named {{name}}. The number of values in the array will be decreased by the number of the {{index}}es specified. If the {{index}}th value does not exist, it is silently ignored. With the +-i+ (+--insert+) option, the built-in inserts {{value}}s into the array named {{name}}. The number of values in the array will be increased by the number of the {{value}}s specified. The values are inserted between the {{index}}th and next values. If {{index}} is zero, the values are inserted before the first value. If {{index}} is larger than the number of values in the array, the values are appended after the last element. With the +-s+ (+--set+) option, the built-in sets {{value}} as the {{index}}th value of the array named {{name}}. The array must have at least {{index}} values. [[options]] == Options +-d+:: +--delete+:: Delete array values. +-i+:: +--insert+:: Insert array values. +-s+:: +--set+:: Set an array value. [[operands]] == Operands {{name}}:: The name of an array to operate on. {{index}}:: The index to an array element. The first element has the index of 1. {{value}}:: A string to which the array element is set. [[exitstatus]] == Exit status The exit status of the array built-in is zero unless there is any error. [[notes]] == Notes The array built-in is not defined in the POSIX standard. The command +array {{name}} {{value}}...+ is equivalent to the assignment +{{name}}=({{value}}...)+. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_bg.txt000066400000000000000000000023231354143602500144540ustar00rootroot00000000000000= Bg built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Bg built-in The dfn:[bg built-in] resumes a job in the background. [[syntax]] == Syntax - +bg [{{job}}...]+ [[description]] == Description The bg built-in sends the SIGCONT signal to the specified job. As a result, the job is resumed in the background (if it has been suspended). The name of the job is printed when the job is resumed. The built-in can be used only when link:job.html[job control] is enabled. [[operands]] == Operands {{job}}:: The link:job.html#jobid[job ID] of the job to be resumed. + More than one job can be specified at a time. The current job is resumed if none is specified. + The percent sign (+%+) at the beginning of a job ID can be omitted if the shell is not in the link:posix.html[POSIXly-correct mode]. [[exitstatus]] == Exit status The exit status of the bg built-in is zero unless there is any error. [[notes]] == Notes The bg built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard provides that the built-in shall have no effect when the job is already running. The bg built-in of yash, however, always sends the SIGCONT signal to the job. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_bindkey.txt000066400000000000000000000035331354143602500155150ustar00rootroot00000000000000= Bindkey built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Bindkey built-in The dfn:[bindkey built-in] prints or modifies key bindings used in link:lineedit.html[line-editing]. [[syntax]] == Syntax - +bindkey -aev [{{key}} [{{command}}]]+ - +bindkey -l+ [[description]] == Description When executed with the +-l+ (+--list+) option, the built-in lists all available link:lineedit.html#commands[line-editing commands] to the standard output. When executed with one of the other options, the built-in prints or modifies key bindings for the link:lineedit.html#modes[editing mode] specified by the option: - Without {{key}} or {{command}}, all currently defined bindings are printed to the standard output in a form that can be parsed as commands that restore the current bindings when executed. - With {{key}} but without {{command}}, only the binding for the given {{key}} is printed. - With {{key}} and {{command}}, {{key}} is bound to {{command}}. [[options]] == Options +-a+:: +--vi-command+:: Print or modify bindings for the vi command mode. +-e+:: +--emacs+:: Print or modify bindings for the emacs mode. +-v+:: +--vi-insert+:: Print or modify bindings for the vi insert mode. [[operands]] == Operands {{key}}:: A character sequence of one or more keys that is bound to an editing command. The sequence may include link:lineedit.html#escape[escape sequences]. {{command}}:: A link:lineedit.html#commands[line-editing command] to which {{key}} is bound. If {{command}} is a single hyphen (+-+), {{key}} is unbound. [[exitstatus]] == Exit status The exit status of the bindkey built-in is zero unless there is any error. [[notes]] == Notes The bindkey built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_break.txt000066400000000000000000000035301354143602500151510ustar00rootroot00000000000000= Break built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Break built-in The dfn:[break built-in] aborts a loop being executed. [[syntax]] == Syntax - +break [{{nest}}]+ - +break -i+ [[description]] == Description When executed without the +-i+ (+--iteration+) option, the built-in aborts a currently executed link:syntax.html#for[for], link:syntax.html#while-until[while], or link:syntax.html#while-until[until] loop. When executed in nested loops, it aborts the {{nest}}th innermost loop. The default {{nest}} is one. If the number of currently executed nested loops is less than {{nest}}, the built-in aborts the outermost loop. When executed with the +-i+ (+--iteration+) option, the built-in aborts the currently executed (innermost) link:_eval.html#iter[iterative execution]. [[options]] == Options +-i+:: +--iteration+:: Abort an iterative execution instead of a loop. [[operands]] == Operands {{nest}}:: The number of loops to abort, which must be a positive integer. [[exitstatus]] == Exit status The exit status of the break built-in is: - zero if a loop was successfully aborted. - that of the command that was executed just before the break built-in if an iterative execution was successfully aborted. [[notes]] == Notes The break built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the break built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. Treatment of currently executed loops that are not lexically enclosing the break built-in is unspecified in POSIX. Examples of such loops include: - A loop invoking a function in which the break built-in is used. - A loop in which a link:_trap.html[trap] action is executed in which the break built-in is used. Yash does not allow breaking such loops. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_cd.txt000066400000000000000000000067201354143602500144570ustar00rootroot00000000000000= Cd built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Cd built-in The dfn:[cd built-in] changes the working directory. [[syntax]] == Syntax - +cd [-L|-P] [{{directory}}]+ [[description]] == Description The cd built-in changes the working directory to the directory specified by the operand. The pathname of the new working directory is assigned to the link:params.html#sv-pwd[+PWD+ variable], whose previous value is again assigned to the link:params.html#sv-oldpwd[+OLDPWD+ variable]. The new +PWD+ value will not contain any +.+ or +..+ components except when the shell is in the link:posix.html[POSIXly-correct mode] and the new pathname begins with +/..+. If {{directory}} is a relative path that does not start with `.' or `..', paths in the link:params.html#sv-cdpath[+CDPATH+ variable] are searched to find a new working directory. The search is done in a manner similar to the last step of link:exec.html#search[command search], but a directory is sought instead of an executable regular file. If a new working directory was found from +CDPATH+, its pathname is printed to the standard output. If no applicable directory was found in the search, {{directory}} is simply treated as a pathname relative to the current working directory. If the working directory was successfully changed, the value of the link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+ variable] is executed as a command unless the shell is in the link:posix.html[POSIXly-correct mode]. If the variable is an link:params.html#arrays[array], its values are executed iteratively (cf. link:_eval.html#iter[eval built-in]). [[options]] == Options +-L+:: +--logical+:: Symbolic links in the pathname of the new working directory are not resolved. The new value of the +PWD+ may include pathname components that are symbolic links. +-P+:: +--physical+:: Symbolic links in the pathname of the new working directory are resolved. The new value of the +PWD+ variable never includes pathname components that are symbolic links. +--default-directory={{directory}}+:: If this option is specified and the {{directory}} operand is omitted, the argument to this option is used for the {{directory}} operand. If the {{directory}} operand is specified, this option is ignored. The +-L+ (+--logical+) and +-P+ (+--physical+) options are mutually exclusive: only the last specified one is effective. If neither is specified, +-L+ is assumed. [[operands]] == Operands {{directory}}:: The pathname of the new working directory. + If {{directory}} is a single hyphen (`-'), the value of the link:params.html#sv-oldpwd[+OLDPWD+ variable] is assumed for the new directory pathname, which is printed to the standard output. + If {{directory}} is omitted, the working directory is changed to the directory specified by the +--default-directory=...+ option. If that option is not specified either, the default is the link:params.html#sv-home[home directory]. [[exitstatus]] == Exit status The exit status of the cd built-in is zero if the working directory was successfully changed and non-zero if there was an error. [[notes]] == Notes The cd built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard does not define the use of the +YASH_AFTER_CD+ variable or the +--default-directory=...+ option. The standard does not allow using an option with a single hyphen operand. The exit status of the commands in the +YASH_AFTER_CD+ variable does not affect that of the cd built-in. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_colon.txt000066400000000000000000000013301354143602500151730ustar00rootroot00000000000000= Colon built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Colon built-in The dfn:[colon built-in] does nothing. [[syntax]] == Syntax - +: [{{argument}}...]+ [[description]] == Description The colon built-in does nothing. Any command line arguments are ignored. [[exitstatus]] == Exit status The exit status of the colon built-in is zero. [[notes]] == Notes The colon built-in is a link:builtin.html#types[special built-in]. Arguments are link:expand.html[expanded] and link:redir.html[redirections] are performed as usual. The colon and link:_true.html[true] built-ins have the same effect, but colon is a special built-in while true is a semi-special. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_command.txt000066400000000000000000000067351354143602500155150ustar00rootroot00000000000000= Command built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Command built-in The dfn:[command built-in] executes or identifies a command. [[syntax]] == Syntax - +command [-befp] {{command}} [{{argument}}...]+ - +command -v|-V [-abefkp] {{command}}...+ [[description]] == Description Without the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option, the built-in executes {{command}} with {{argument}}s in the same manner as the last step of link:exec.html#simple[execution of simple commands]. The command is treated as a built-in or external command or a function according to the options specified to the command built-in. The shell does not exit on argument syntax error etc. even if the command is a link:builtin.html#types[special built-in] With the +-v+ (+--identify+) option, {{command}} is identified. If the command is found in link:params.html#sv-path[+$PATH+], its full pathname is printed. If it is a link:syntax.html#tokens[keyword], link:exec.html#function[function], or link:builtin.html[built-in] that is not found in +$PATH+, the command name is simply printed. If it is an link:syntax.html#aliases[alias], it is printed in the form like `alias ll='ls -l'`. If the command is not found, nothing is printed and the exit status is non-zero. The +-V+ (+--verbose-identify+) option is similar to the +-v+ (+--identify+) option, but the output format is more human-friendly. [[options]] == Options +-a+:: +--alias+:: Search for the command as an link:syntax.html#aliases[alias]. Must be used with the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option. +-b+:: +--builtin-command+:: Search for the command as a link:builtin.html[built-in]. +-e+:: +--external-command+:: Search for the command as an external command. +-f+:: +--function+:: Search for the command as a link:exec.html#function[function]. +-k+:: +--keyword+:: Search for the command as a link:syntax.html#tokens[keyword]. Must be used with the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option. +-p+:: +--standard-path+:: Search the system's default +PATH+ instead of the current link:params.html#sv-path[+$PATH+]. +-v+:: +--identify+:: Identify {{command}}s and print in the format defined in the POSIX standard. +-V+:: +--verbose-identify+:: Identify {{command}}s and print in a human-friendly format. If none of the +-a+ (+--alias+), +-b+ (+--builtin-command+), +-e+ (+--external-command+), +-f+ (+--function+), and +-k+ (+--keyword+) options is specified, the following defaults are assumed: Without the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option:: +-b -e+ With the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option:: +-a -b -e -f -k+ [[operands]] == Operands {{command}}:: A command to be executed or identified. {{argument}}...:: Arguments passed to the executed command. [[exitstatus]] == Exit status The exit status of the command built-in is: Without the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option:: the exit status of the executed command. With the +-v+ (+--identify+) or +-V+ (+--verbose-identify+) option:: zero unless there is any error. [[notes]] == Notes The command built-in is a link:builtin.html#types[semi-special built-in]. In the link:posix.html[POSIXly-correct mode], options other than +-p+, +-v+, and +-V+ cannot be used and at most one {{command}} can be specified. The POSIX standard does not allow specifying both +-v+ and +-V+ together, but yash does (only the last specified one is effective). // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_complete.txt000066400000000000000000000112401354143602500156720ustar00rootroot00000000000000= Complete built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Complete built-in The dfn:[complete built-in] generates completion candidates. This built-in can only be executed from completion functions during link:lineedit.html#completion[command line completion]. [[syntax]] == Syntax - +complete [-A {{pattern}}] [-R {{pattern}}] [-T] [-P {{prefix}}] [-S {{suffix}}] [-abcdfghjkuv] [[-O] [-D {{description}}] {{word}}...]+ [[description]] == Description The built-in generates completion candidates according to the specified arguments. No matter how candidates are generated, only candidates that match the word being completed are generated. [[options]] == Options +-A {{pattern}}+:: +--accept={{pattern}}+:: Only accept candidates that match the link:pattern.html[pattern] specified by this option. When more than one of this option is specified, only candidates that match all of the patterns are generated. +-D {{description}}+:: +--description={{description}}+:: Give a description of the {{word}} candidates. The description is shown beside the candidates in the candidate list. +-O+:: +--option+:: The candidates are treated as command line options. A hyphen is prepended to each candidate that is treated as an option. +-P {{prefix}}+:: +--prefix={{prefix}}+:: Ignore {{prefix}} of the word being completed when generating candidates. The specified {{prefix}} must be initial part of the word. + If the word being completed is `file:///home/user/docume` for example, the command line `complete -P file:// -f` will generate pathname candidates that complete `/home/user/docume`. +-R {{pattern}}+:: +--reject={{pattern}}+:: Reject candidates that match the link:pattern.html[pattern] specified by this option. When more than one of this option is specified, only candidates that match none of the patterns are generated. +-S {{suffix}}+:: +--suffix={{suffix}}+:: Append {{suffix}} to each generated candidate. +-T+:: +--no-termination+:: Do not append a space after the word is completed. Without this option, a space is appended to the completed word so that you do not have to enter a space before the next word. === Options that select candidate types +-a+:: +--alias+:: link:syntax.html#aliases[Aliases]. (same as +--normal-alias --global-alias+) +--array-variable+:: link:params.html#arrays[Arrays]. +--bindkey+:: link:lineedit.html#commands[Line-editing commands] the link:_bindkey.html[+bindkey+ built-in] accepts. +-b+:: +--builtin-command+:: link:builtin.html[Built-in commands]. (same as +--special-builtin --semi-special-builtin --regular-builtin+) +-c+:: +--command+:: Commands. (same as +--builtin-command --external-command --function+) +-d+:: +--directory+:: Directories. +--dirstack-index+:: Valid indices of the link:_dirs.html[directory stack]. +--executable-file+:: Executable regular files. +--external-command+:: External commands. +-f+:: +--file+:: Files (including directories). +--finished-job+:: link:job.html#jobid[Job IDs] of finished jobs. +--function+:: link:exec.html#function[Functions]. +--global-alias+:: Global link:syntax.html#aliases[aliases]. +-g+:: +--group+:: User groups. +-h+:: +--hostname+:: Host names. +-j+:: +--job+:: link:job.html#jobid[Job IDs]. +-k+:: +--keyword+:: link:syntax.html#tokens[Keywords]. +--normal-alias+:: Normal link:syntax.html#aliases[aliases]. +--regular-builtin+:: link:builtin.html#types[Regular built-in commands]. +--running-job+:: link:job.html#jobid[Job IDs] of jobs that are being executed. +--scalar-variable+:: link:params.html#variables[Variables] that are not arrays. +--semi-special-builtin+:: link:builtin.html#types[Semi-special built-in commands]. +--signal+:: Signals. +--special-builtin+:: link:builtin.html#types[Special built-in commands]. +--stopped-job+:: link:job.html#jobid[Job IDs] of jobs that are suspended. +-u+:: +--username+:: Users' log-in names. +-v+:: +--variable+:: link:params.html#variables[Variables]. If the +-d+ (+--directory+) option is specified without the +-f+ (+--file+) option, the +-S / -T+ options are assumed. Generated candidates for link:job.html#jobid[job IDs] do not have leading percent signs (+%+). If the word being completed starts with a percent sign, the +-P %+ option should be specified. [[operands]] == Operands Operands are treated as completion candidates. [[exitstatus]] == Exit status The exit status of the built-in is zero if one or more candidates were generated, one if no candidates were generated, or larger than one if an error occurred. [[notes]] == Notes The complete built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_continue.txt000066400000000000000000000037551354143602500157220ustar00rootroot00000000000000= Continue built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Continue built-in The dfn:[continue built-in] skips an iteration of a loop being executed. [[syntax]] == Syntax - +continue [{{nest}}]+ - +continue -i+ [[description]] == Description When executed without the +-i+ (+--iteration+) option, the built-in aborts the current iteration of link:syntax.html#for[for], link:syntax.html#while-until[while], or link:syntax.html#while-until[until] loop and starts the next iteration of the loop. When executed in nested loops, it affects the {{nest}}th innermost loop. The default {{nest}} is one. If the number of currently executed nested loops is less than {{nest}}, the built-in affects the outermost loop. When executed with the +-i+ (+--iteration+) option, the built-in aborts the current iteration of (innermost) link:_eval.html#iter[iterative execution]. [[options]] == Options +-i+:: +--iteration+:: Skip an iterative execution instead of a loop. [[operands]] == Operands {{nest}}:: The {{nest}}th innermost loop is affected. {{nest}} must be a positive integer. [[exitstatus]] == Exit status The exit status of the continue built-in is: - zero if loop iteration was successfully skipped. - that of the command that was executed just before the continue built-in if iterative execution was successfully skipped. [[notes]] == Notes The continue built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the continue built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. Treatment of currently executed loops that are not lexically enclosing the continue built-in is unspecified in POSIX. Examples of such loops include: - A loop invoking a link:exec.html#function[function] in which the continue built-in is used - A loop in which a link:_trap.html[trap] action is executed in which the continue built-in is used Yash does not allow continuing such loops. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_dirs.txt000066400000000000000000000040031354143602500150220ustar00rootroot00000000000000= Dirs built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Dirs built-in The dfn:[dirs built-in] prints the contents of the directory stack. [[syntax]] == Syntax - +dirs [-cv] [{{index}}..]+ [[description]] == Description The dfn:[directory stack] is a feature that records history of working directories. You can use the link:_pushd.html[pushd built-in] to save a working directory in the directory stack, the link:_popd.html[popd built-in] to recall the saved working directory, and the dirs built-in to see the stack contents. Those built-ins use the link:params.html#sv-dirstack[+DIRSTACK+ array] and the link:params.html#sv-pwd[+PWD+ variable] to save the stack contents. Modifying the array means modifying the stack contents. Directory stack entries are indexed by signed integers. The entry of index +0 is the current working directory, +1 is the last saved directory, +2 is the second last, and so on. Negative indices are in the reverse order: the entry of index -0 is the first saved directory, -1 is the second, and -{{n}} is the current working directory if the stack has {{n}} entries, When executed without the +-c+ (+--clear+) option, the dirs built-in prints the current contents of the directory stack to the standard output. With the +-c+ (+--clear+) option, the built-in clears the directory stack. [[options]] == Options +-c+:: +--clear+:: Clear the directory stack contents except for the current working directory, which has index +0. +-v+:: +--verbose+:: Print indices when printing stack contents. [[operands]] == Operands {{index}}:: The index of a stack entry to be printed. + You can specify more than one index. If you do not specify any index, all the entries are printed. [[exitstatus]] == Exit status The exit status of the dirs built-in is zero unless there is any error. [[notes]] == Notes The dirs built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_disown.txt000066400000000000000000000021351354143602500153700ustar00rootroot00000000000000= Disown built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Disown built-in The dfn:[disown built-in] removes jobs. [[syntax]] == Syntax - +disown [-a] [{{job}}...}+ [[description]] == Description The disown built-in removes the specified jobs from the job list. The removed jobs will no longer be link:job.html[job-controlled], but the job processes continue execution (unless they have been suspended). [[options]] == Options +-a+:: +--all+:: Removes all jobs. [[operands]] == Operands {{job}}:: The link:job.html#jobid[job ID] of the job to be removed. + You can specify more than one job ID. If you do not specify any job ID, the current job is removed. If the shell is not in the link:posix.html[POSIXly-correct mode], the %-prefix of the job ID can be omitted. [[exitstatus]] == Exit status The exit status of the disown built-in is zero unless there is any error. [[notes]] == Notes The disown built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_dot.txt000066400000000000000000000047661354143602500146670ustar00rootroot00000000000000= Dot built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Dot built-in The dfn:[dot built-in] reads a file and executes commands in it. [[syntax]] == Syntax - +. [-AL] {{file}} [{{argument}}...]+ [[description]] == Description The dot built-in reads the specified {{file}}, parses its contents as commands, and executes them in the current link:exec.html#environment[command execution environment]. If {{argument}}s are specified, link:params.html#positional[positional parameters] are temporarily set to them. The positional parameters will be restored when the dot built-in finishes. If no {{argument}}s are specified, the positional parameters are not changed. If {{file}} does not contain any slashes, the shell searches link:params.html#sv-path[+$PATH+] for a readable (but not necessarily executable) shell script file whose name is {{file}} in the same manner as link:exec.html#search[command search]. If no such file was found, the shell searches the current working directory for a file unless in the link:posix.html[POSIXly-correct mode]. To ensure that the file in the current working directory is used, start {{file}} with `./'. [[options]] == Options +-A+:: +--no-alias+:: Disable alias substitution while parsing. +-L+:: +--autoload+:: Search link:params.html#sv-yash_loadpath[+$YASH_LOADPATH+] instead of link:params.html#sv-path[+$PATH+], regardless of whether {{file}} contains slashes. The {{file}} value is not considered relative to the current working directory. The dot built-in treats as operands any command line arguments after the first operand. [[operands]] == Operands {{file}}:: The pathname of a file to be read. {{arguments}}...:: Strings to which positional parameters are set while execution. [[exitstatus]] == Exit status The exit status of the dot built-in is that of the last command executed. The exit status is zero if the file contains no commands to execute and non-zero if a file was not found or could not be opened. [[notes]] == Notes The dot built-in is a link:builtin.html#types[special built-in]. A link:interact.html[non-interactive] shell immediately exits with a non-zero exit status if the dot built-in fails to find or open a file to execute. The POSIX standard defines no options for the dot built-in; the built-in accepts no options in the POSIXly-correct mode. The POSIX standard does not define the {{arguments}}... operands. It is an error to specify the {{arguments}}... operands in the POSIXly-correct mode. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_echo.txt000066400000000000000000000062571354143602500150140ustar00rootroot00000000000000= Echo built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Echo built-in The dfn:[echo built-in] prints its arguments. [[syntax]] == Syntax - +echo [{{string}}...]+ The built-in treats all command line arguments as operands except for the options described below. Any word that cannot be parsed as an acceptable option is treated as an operand. Options must precede all operands. Syntax errors never happen in the echo built-in. [[description]] == Description The echo built-in prints the operand {{string}}s followed by a newline to the standard output. The {{string}}s are each separated by a space. [[escapes]] === Escape sequences The +ECHO_STYLE+ variable and the +-e+ option enable escape sequences that are replaced with corresponding characters: +\a+:: Bell character (ASCII code: 7) +\b+:: Backspace (ASCII code: 8) +\c+:: Nothing. After this escape sequence, no characters are printed at all. +\e+:: Escape character (ASCII code: 27) +\f+:: Form feed character (ASCII code: 12) +\n+:: Newline character (ASCII code: 10) +\r+:: Carriage return character (ASCII code: 13) +\t+:: Horizontal tab character (ASCII code: 9) +\v+:: Vertical tab character (ASCII code: 11) +\\+:: Backslash +\0{{xxx}}+:: Character whose code is {{xxx}}, where {{xxx}} is an octal number of at most three digits. When escape sequences are not enabled, they are just printed intact. [[echo_style]] === +ECHO_STYLE+ variable The link:params.html#sv-echo_style[+ECHO_STYLE+ variable] defines which options are accepted and whether escape sequences are enabled by default. The variable value should be set to one of the following: +SYSV+:: +XSI+:: No options are accepted. Escape sequences are always enabled. +BSD+:: The +-n+ option is accepted. Escape sequences are never enabled. +GNU+:: The +-n+, +-e+, and +-E+ options are accepted. Escape sequences are not enabled by default, but can be enabled by the +-e+ option. +ZSH+:: The +-n+, +-e+, and +-E+ options are accepted. Escape sequences are enabled by default, but can be disabled by the +-E+ option. +DASH+:: The +-n+ option is accepted. Escape sequences are always enabled. +RAW+:: No options are accepted. Escape sequences are never enabled. When the +ECHO_STYLE+ variable is not set, it defaults to +SYSV+. [[options]] == Options +-n+:: Do not print a newline at the end. +-e+:: Enable escape sequences. +-E+:: Disable escape sequences. [[exitstatus]] == Exit status The exit status of the echo built-in is zero unless there is any error. [[notes]] == Notes The POSIX standard does not define the +ECHO_STYLE+ variable nor any options for the built-in. According to POSIX, the behavior of the built-in is implementation-defined when the first argument is +-n+ or when any argument contains a backslash. For maximum portability, the link:_printf.html[printf built-in] should be preferred over the echo built-in. Although many values for the +ECHO_STYLE+ variable are defined on the basis of other existing implementations, yash is not intended to exactly imitate those originals. Zsh's echo built-in interprets a single hyphen argument as a separator between options and operands. Yash does not support such use of hyphen. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_eval.txt000066400000000000000000000041011354143602500150070ustar00rootroot00000000000000= Eval built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Eval built-in The dfn:[eval built-in] evaluates operands as commands. [[syntax]] == Syntax - +eval [-i] [{{command}}...]+ The eval built-in requires that all options precede operands. Any command line arguments after the first operand are all treated as operands. [[description]] == Description The eval parses operands as commands and executes them in the current link:exec.html#environment[command execution environment]. When executed without the +-i+ (+--iteration+) option, all the operands are concatenated into one string (with a space inserted between each operand) and parsed/executed at once. With the +-i+ (+--iteration+) option, the built-in performs dfn:iter[iterative execution]: operands are parsed/executed one by one. If the link:_continue.html[continue built-in] is executed with the +-i+ (+--iteration+) option during the iterative execution, the execution of the current operand is aborted and the next operand is parsed/executed immediately. The link:_break.html[break built-in] with the +-i+ (+--iteration+) option is similar but the remaining operands are not parsed/executed. The value of the link:params.html#sp-hash[+?+ special parameter] is saved before the iterative execution is started. The parameter value is restored to the saved one after each iteration. [[options]] == Options +-i+:: +--iteration+:: Perform iterative execution. [[operands]] == Operands {{command}}:: A string that is parsed and executed as commands. [[exitstatus]] == Exit status The exit status is zero if no {{command}} was specified or {{command}} contained no actual command that can be executed. Otherwise, that is, if the eval built-in executed one or more commands, the exit status of the eval built-in is that of the last executed command. [[notes]] == Notes The eval built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the eval built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_exec.txt000066400000000000000000000060751354143602500150200ustar00rootroot00000000000000= Exec built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Exec built-in The dfn:[exec built-in] replaces the shell process with another external command. [[syntax]] == Syntax - +exec [-cf] [-a {{name}}] [{{command}} [{{argument}}...]]+ The exec built-in requires that all options precede operands. It is important so that options to the exec built-in are not confused with options to {{command}}. Any command line arguments after {{command}} are treated as {{argument}}s. [[description]] == Description When the exec built-in is executed with {{command}}, the shell executes {{command}} with {{argument}}s in a manner similar to the last step of link:exec.html#simple[execution of a simple command]. The differences are that {{command}} is always treated as an external command ignoring any existing functions and built-ins and that the exec system call that starts the external command is called in the current link:exec.html#environment[command execution environment] instead of a subshell, replacing the shell process with the new command process. If the shell is in the link:posix.html[POSIXly-correct mode] or not link:interact.html[interactive], failure in execution of {{command}} causes the shell to exit immediately. If an interactive shell that is not in the POSIXly-correct mode has a stopped link:job.html[job], the shell prints a warning message and refuses to execute {{command}}. Once the shell process is replaced with an external command, information about the shell's jobs is lost, so you will have to resume or kill the stopped jobs by sending signals by hand. To force the shell to execute {{command}} regardless, specify the +-f+ (+--force+) option. When executed without {{command}}, the built-in does nothing. As a side effect, however, link:redir.html[redirection] applied to the built-in remains in the current link:exec.html#environment[command execution environment] even after the built-in finished. [[options]] == Options +-a {{name}}+:: +--as={{name}}+:: Pass {{name}}, instead of {{command}}, to the external command as its name. +-c+:: +--clear+:: Pass to the external command only variables that are assigned in the link:exec.html#simple[simple command] in which the built-in is being executed. Other environment variables are not passed to the command. +-f+:: +--force+:: Suppress warnings that would prevent command execution. [[operands]] == Operands {{command}}:: An external command to be executed. {{argument}}...:: Arguments to be passed to the command. [[exitstatus]] == Exit status If the shell process was successfully replaced with the external command, there is no exit status since the shell process no longer exists. The exit status is: - 127 if the command was not found, - 126 if the command was found but could not be executed, and - zero if no {{command}} was specified. [[notes]] == Notes The exec built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the exec built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_exit.txt000066400000000000000000000042571354143602500150450ustar00rootroot00000000000000= Exit built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Exit built-in The dfn:[exit built-in] causes the shell process to exit. [[syntax]] == Syntax - +exit [-f] [{{exit_status}}]+ [[description]] == Description The exit built-in causes the current shell (or link:exec.html#subshell[subshell]) process to exit. If an interactive shell has a stopped link:job.html[job], the shell prints a warning message and refuses to exit. To force the shell to exit regardless, specify the +-f+ (+--force+) option or execute the built-in twice in a row. If an EXIT link:_trap.html[trap] has been set, the shell executes the trap before exiting. [[options]] == Options +-f+:: +--force+:: Suppress warnings that would prevent the shell from exiting. [[operands]] == Operands {{exit_status}}:: A non-negative integer that will be the exit status of the exiting shell. + If this operand is omitted, the exit status of the shell will be that of the last command executed before the exit built-in (but, if the built-in is executed during a link:_trap.html[trap], the exit status will be that of the last command before the trap is entered). + If {{exit_status}} is 256 or larger, the actual exit status will be the remainder of {{exit_status}} divided by 256. [[exitstatus]] == Exit status Because the built-in causes the shell to exit, there is no exit status of the built-in. As an exception, if the shell refused to exit, the exit status of the built-in is non-zero. [[notes]] == Notes The exit built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the exit built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. The POSIX standard provides that the {{exit_status}} operand should be between 0 and 255 (inclusive). Yash accepts integers larger than 255 as an extension. If the built-in is executed during an EXIT link:_trap.html[trap], the shell just exits without executing the trap again. If {{exit_status}} was not specified, the exit status of the shell is what the exit status would be if the trap had not been set. See also link:exec.html#exit[Termination of the shell]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_export.txt000066400000000000000000000012671354143602500154130ustar00rootroot00000000000000= Export built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Export built-in The dfn:[export built-in] marks variables for export to child processes. [[syntax]] == Syntax - +export [-prX] [{{name}}[={{value}}]...]+ [[description]] == Description The export built-in is equivalent to the link:_typeset.html[typeset built-in] with the +-gx+ option. [[notes]] == Notes The export built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines the +-p+ option only; other options cannot be used in the link:posix.html[POSIXly-correct mode]. The POSIX does not allow using the option together with operands. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_false.txt000066400000000000000000000007741354143602500151660ustar00rootroot00000000000000= False built-in :encoding: UTF-8 :lang: en //:title: Yash manual - False built-in The dfn:[false built-in] does nothing unsuccessfully. [[syntax]] == Syntax - +false+ [[description]] == Description The false built-in does nothing. Any command line arguments are ignored. [[exitstatus]] == Exit status The exit status of the false built-in is non-zero. [[notes]] == Notes The false built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_fc.txt000066400000000000000000000071501354143602500144570ustar00rootroot00000000000000= Fc built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Fc built-in The dfn:[fc built-in] re-executes or prints commands from link:interact.html#history[command history]. [[syntax]] == Syntax - +fc [-qr] [-e {{editor}}] [{{start}} [{{end}}]]+ - +fc -s[q] [{{old}}={{new}}] [{{start}}]+ - +fc -l[nrv] [{{start}} [{{end}}]]+ [[description]] == Description When executed without the +-l+ (+--list+) option, the built-in executes the commands in the link:interact.html#history[command history] range specified by the operands. If the +-s+ (+--silent+) option is not specified, the shell invokes an editor which allows you to edit the commands before they are executed. The commands are executed when you quit the editor. If the +-s+ (+--silent+) option is specified, the commands are immediately executed. In either case, the executed commands are printed to the standard output and added to the history. When executed with the +-l+ (+--list+) option, the built-in prints the commands in the command history range specified by the operands. By default, commands are printed with their history entry numbers, but output format can be changed using the +-n+ (+--no-numbers)+) and +-v+ (+--verbose+) options. [[options]] == Options +-e {{editor}}+:: +--editor={{editor}}+:: Specify an editor that is used to edit commands. + If this option is not specified, the value of the link:params.html#sv-fcedit[+FCEDIT+ variable] is used. If the variable is not set either, +ed+ is used. +-l+:: +--list+:: Print command history entries. +-n+:: +--no-numbers+:: Don't print entry numbers when printing history entries. +-q+:: +--quiet+:: Don't print commands before executing. +-r+:: +--reverse+:: Reverse the order of command entries in the range. +-s+:: +--silent+:: Execute commands without editing them. +-v+:: +--verbose+:: Print execution time before each history entry when printing. [[operands]] == Operands {{start}} and {{end}}:: The {{start}} and {{end}} operands specify a range of command history entries that are executed or printed. If one of the operands is an integer, it is treated as a history entry number. A negative integer means the {{n}}th most recent entry where {{n}} is the absolute value of the integer. If one of the operands is not an integer, it is treated as part of a command string: the most recent entry that starts with the string is selected as the start or end of the range. + If the first entry of the range that is specified by {{start}} is newer than the last entry of the range that is specified by {{end}}, the range is reversed as if the +-r+ (+--reverse+) option was specified. (If the option is already specified, it is cancelled.) + The default values for {{start}} and {{end}} are: + [width="50%",options="header"] |=== | |with +-l+ |without +-l+ |{{start}} |-16 |-1 |{{end}} |-16 |same as {{start}} |=== {{old}}={{new}}:: An operand of this format replaces part of the command string. If the command string contains {{old}}, it is replaced with {{new}} and the new string is executed. Only the first occurrence of {{old}} is replaced. [[exitstatus]] == Exit status If commands was executed, the exit status of the fc built-in is that of the last executed command. Otherwise, the exit status is zero unless there is any error. [[notes]] == Notes The fc built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard does not define the +-q+ (+--quiet+) or +-v+ (+--verbose+) options, so they cannot be used in the link:posix.html[POSIXly-correct mode]. Command history cannot be modified during link:lineedit.html[line-editing]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_fg.txt000066400000000000000000000026271354143602500144670ustar00rootroot00000000000000= Fg built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Fg built-in The dfn:[fg built-in] resumes a job in the foreground. [[syntax]] == Syntax - +fg [{{job}}...]+ [[description]] == Description The fg built-in brings the specified job to the foreground and sends the SIGCONT signal to the job. As a result, the job is resumed in the foreground (if it has been suspended). The built-in then waits for the job to finish and returns the exit status of it. The name of the job is printed when the job is resumed. When not in the link:posix.html[POSIXly-correct mode], the job number is also printed. The built-in can be used only when link:job.html[job control] is enabled. [[operands]] == Operands {{job}}:: The link:job.html#jobid[job ID] of the job to be resumed. + If more than one job is specified, they are resumed in order, one at a time. The current job is resumed if none is specified. + The percent sign (+%+) at the beginning of a job ID can be omitted if the shell is not in the link:posix.html[POSIXly-correct mode]. [[exitstatus]] == Exit status The exit status of the fg built-in is that of the (last) job resumed. The exit status is non-zero when there was some error. [[notes]] == Notes The fg built-in is a link:builtin.html#types[semi-special built-in]. You cannot specify more than one job in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_getopts.txt000066400000000000000000000072021354143602500155520ustar00rootroot00000000000000= Getopts built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Getopts built-in The dfn:[getopts built-in] parses command options. [[syntax]] == Syntax - +getopts {{optionlist}} {{variable}} [{{argument}}...]+ [[description]] == Description The getopts built-in parses link:builtin.html#argsyntax[single-character options] that appear in {{argument}}s. Each time the built-in is invoked, it parses one option and assigns the option character to {{variable}}. The {{optionlist}} operand is a list of option characters that should be accepted by the parser. In {{optionlist}}, an option that takes an argument should be specified as the option character followed by a colon. For example, if you want the +-a+, +-b+ and +-c+ options to be parsed and the +-b+ option to take an argument, then {{optionlist}} should be +ab:c+. When an option that takes an argument is parsed, the argument is assigned to the link:params.html#sv-optarg[+OPTARG+ variable]. When an option that is not specified in {{optionlist}} is found or when an option argument is missing, the result depends on the first character of {{optionlist}}: - If {{optionlist}} starts with a colon, the option character is assigned to the +OPTARG+ variable and {{variable}} is set to either +?+ (when the option is not in {{optionlist}}) or +:+ (when the option argument is missing). - Otherwise, {{variable}} is set to +?+, the +OPTARG+ variable is unset, and an error message is printed. The built-in parses one option for each execution. For all options in a set of command line arguments to be parsed, the built-in has to be executed repeatedly with the same arguments. The built-in uses the link:params.html#sv-optind[+OPTIND+ variable] to remember which {{argument}} should be parsed next. When the built-in is invoked for the first time, the variable value must be +1+, which is the default value. You must not modify the variable until all the options have been parsed, when the built-in sets the variable to the index of the first operand in {{argument}}s. (If there are no operands, it will be set to the number of {{argument}}s plus one.) When you want to start parsing a new set of {{argument}}s, you have to reset the +OPTIND+ variable to +1+ beforehand. [[operands]] == Operands {{optionlist}}:: A list of options that should be accepted as valid options in parsing. {{variable}}:: The name of a variable the result is to be assigned to. {{argument}}s:: Command line arguments that are to be parsed. + When no {{argument}}s are given, the link:params.html#positional[positional parameters] are parsed. [[exitstatus]] == Exit status If an option is found, whether or not it is specified in {{optionlist}}, the exit status is zero. If there is no more option to be parsed, the exit status is non-zero. [[example]] == Example ---- aopt=false bopt= copt=false while getopts ab:c opt do case $opt in a) aopt=true ;; b) bopt=$OPTARG ;; c) copt=true ;; \?) return 2 ;; esac done if $aopt; then echo Option -a specified; fi if [ -n "$bopt" ]; then echo Option -b $bopt specified; fi if $copt; then echo Option -c specified; fi shift $((OPTIND - 1)) echo Operands are: $* ---- [[notes]] == Notes In {{argument}}s that are parsed, options must precede operands. The built-in ends parsing when it encounters the first operand. The getopts built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard does not specify what will happen when the +OPTIND+ variable is assigned a value other than +1+. In the link:posix.html[POSIXly-correct mode], option characters in {{optionlist}} must be alphanumeric. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_hash.txt000066400000000000000000000040401354143602500150050ustar00rootroot00000000000000= Hash built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Hash built-in The dfn:[hash built-in] remembers, forgets, or reports command locations. [[syntax]] == Syntax - +hash {{command}}...+ - +hash -r [{{command}}...]+ - +hash [-a]+ - +hash -d {{user}}...+ - +hash -dr [{{user}}...]+ - +hash -d+ [[description]] == Description When executed with {{command}}s but without options, the built-in immediately performs link:exec.html#search[command path search] and caches {{command}}s' full paths. When executed with the +-r+ (+--remove+) option, it removes the paths of {{command}}s (or all cached paths if none specified) from the cache. When executed without options or {{command}}s, it prints the currently cached paths to the standard output. With the +-d+ (+--directory+) option, the built-in does the same things to the home directory cache, rather than the command path cache. Cached home directory paths are used in link:expand.html#tilde[tilde expansion]. [[options]] == Options +-a+:: +--all+:: Print all cached paths. + Without this option, paths for built-ins are not printed. +-d+:: +--directory+:: Affect the home directory cache instead of the command path cache. +-r+:: +--remove+:: Remove cached paths. [[operands]] == Operands {{command}}:: The name of an external command (that does not contain any slash). {{user}}:: A user name. [[exitstatus]] == Exit status The exit status of the hash built-in is zero unless there is any error. [[notes]] == Notes The shell automatically caches command and directory paths when executing a command or performing tilde expansion, so normally there is no need to use this built-in explicitly to cache paths. Assigning a value to the link:params.html#sv-path[+PATH+ variable] removes all command paths from the cache as if +hash -r+ was executed. The POSIX standard defines the +-r+ option only: other options cannot be used in the link:posix.html[POSIXly-correct mode]. The hash built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_help.txt000066400000000000000000000014001354143602500150070ustar00rootroot00000000000000= Help built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Help built-in The dfn:[help built-in] prints usage of built-ins. [[syntax]] == Syntax - +help [{{built-in}}...]+ [[description]] == Description The help built-in prints a description of {{built-in}}s. [[operands]] == Operands {{built-in}}s:: Names of link:builtin.html[built-ins]. [[exitstatus]] == Exit status The exit status of the help built-in is zero unless there is any error. [[notes]] == Notes The help built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. Many built-ins of yash accept the +--help+ option that prints the same description. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_history.txt000066400000000000000000000041761354143602500155750ustar00rootroot00000000000000= History built-in :encoding: UTF-8 :lang: en //:title: Yash manual - History built-in The dfn:[history built-in] prints or edits link:interact.html#history[command history]. [[syntax]] == Syntax - +history [-cF] [-d {{entry}}] [-s {{command}}] [-r {{file}}] [-w {{file}}] [{{count}}]+ [[description]] == Description The history built-in prints or edits link:interact.html#history[command history]. When executed with an option, the built-in edits history according to the option. If more than one option is specified, each option is processed in order. When executed with the {{count}} operand, the built-in prints the most recent {{count}} history entries to the standard output in the same manner as the link:_fc.html[fc built-in]. When executed with neither options nor operands, the built-in prints the whole history. [[options]] == Options +-c+:: +--clear+:: Clear all history entries completely. +-d {{entry}}+:: +--delete={{entry}}+:: Delete the specified {{entry}}. The {{entry}} should be specified in the same manner as the {{start}} and {{end}} operands of the link:_fc.html[fc built-in]. +-F+:: +--flush-file+:: Rebuild the history file. This operation removes unused old data from the file. +-r {{file}}+:: +--read={{file}}+:: Read command lines from {{file}} and add them to the history. The file contents are treated as lines of simple text. +-s {{command}}+:: +--set={{command}}+:: Add {{command}} as a new history entry after removing the most recent entry. +-w {{file}}+:: +--write={{file}}+:: Write the whole history to {{file}}. Any existing data in the file will be lost. The output format is lines of simple text, each of which is a command string. [[operands]] == Operands {{count}}:: The number of entries to be printed. [[exitstatus]] == Exit status The exit status of the history built-in is zero unless there is any error. [[notes]] == Notes The history built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. Command history cannot be modified during link:lineedit.html[line-editing]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_jobs.txt000066400000000000000000000036101354143602500150210ustar00rootroot00000000000000= Jobs built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Jobs built-in The dfn:[jobs built-in] reports job status. [[syntax]] == Syntax - +jobs [-lnprs] [{{job}}...]+ [[description]] == Description The jobs built-in prints information of link:job.html[jobs] the shell is currently controlling. By default, the following information is printed for each job, line by line: - the job number, - the +++ or +-+ symbol if the job is the current or previous job, respectively, - the status, and - the command string. [[options]] == Options +-l+:: +--verbose+:: Print the process ID, status, and command string for each process in the jobs. +-n+:: +--new+:: Print new jobs only: jobs whose status has never been reported since the status changed. +-p+:: +--pgid-only+:: Print process group IDs of jobs only. +-r+:: +--running-only+:: Print running jobs only. +-s+:: +--stopped-only+:: Print stopped jobs only. [[operands]] == Operands {{job}}s:: The link:job.html#jobid[job IDs] of jobs to be reported. When no {{job}} is specified, all jobs under the shell's control are reported. + The percent sign (+%+) at the beginning of a job ID can be omitted if the shell is not in the link:posix.html[POSIXly-correct mode]. [[exitstatus]] == Exit status The exit status of the jobs built-in is zero unless there is any error. [[notes]] == Notes The jobs built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard defines the +-l+ and +-p+ options only: other options cannot be used in the link:posix.html[POSIXly-correct mode]. In the POSIXly-correct mode, the effect of the +-l+ option is different in that status is reported for each job rather than for each process. The process group ID of a job executed by yash is equal to the process ID of the first command of the link:syntax.html#pipelines[pipeline] that forms the job. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_kill.txt000066400000000000000000000065651354143602500150330ustar00rootroot00000000000000= Kill built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Kill built-in The dfn:[kill built-in] sends a signal to processes. [[syntax]] == Syntax - +kill [-{{signal}}|-s {{signal}}|-n {{signal}}] {{process}}...+ - +kill -l [-v] [{{signal}}...]+ The kill built-in requires that all options precede operands. Any command line arguments after the first operand are all treated as operands. [[description]] == Description When executed without the +-l+ option, the built-in sends a signal to processes. The signal sent can be specified by option. The SIGTERM signal is sent if no signal is specified. When executed with the +-l+ option, the built-in prints information of {{signal}}s to the standard output. If no {{signal}} is specified, information of all signals is printed. [[options]] == Options === Signal-specifying options +-{{signal}}+:: +-s {{signal}}+:: +-n {{signal}}+:: A signal-specifying option specifies a signal to be sent to processes. {{signal}} can be specified by name or number. If number +0+ is specified, the built-in checks if a signal could be sent to the processes but no signal is actually sent. Signal names are case-insensitive. You can specify at most one signal-specifying option at a time. === Other options +-l+:: Print signal information instead of sending a signal. +-v+:: Print more signal information. + Without this option, the built-in prints the signal name only. This option adds the signal number and a short description. + When the +-v+ option is specified, the +-l+ option can be omitted. [[operands]] == Operands {{process}}es:: Specify processes to which a signal is sent. + Processes can be specified by the process ID, the process group ID, or the link:job.html#jobid[job ID]. The process group ID must be prefixed with a hyphen (+-+) so that it is not treated as a process ID. + When +0+ is specified as {{process}}, the signal is sent to the process group to which the shell process belongs. When +-1+ is specified, the signal is sent to all processes on the system. {{signal}}:: Specify a signal of which information is printed. + The signal can be specified by the name, the number, or the exit status of a command that was killed by the signal. [[exitstatus]] == Exit status The exit status of the kill built-in is zero unless there is any error. If the signal was sent to at least one process, the exit status is zero even if the signal was not sent to all of the specified processes. [[notes]] == Notes The kill built-in is a link:builtin.html#types[semi-special built-in]. Command arguments that start with a hyphen should be used with care. The command +kill -1 -2+, for example, sends signal 1 to process group 2 since +-1+ is treated as a signal-specifying option and +-2+ as an operand that specifies a process group. The commands `kill -- -1 -2` and +kill -TERM -1 -2+, on the other hand, treats both +-1+ and +-2+ as operands. The POSIX standard does not define the +-n+ or +-v+ options, so they cannot be used in the link:posix.html[POSIXly-correct mode]. The standard does not allow specifying a signal number as the argument of the +-s+ option or a signal name as the {{signal}} operand. The standard requires signal names to be specified without the +SIG+ prefix, like +INT+ and +QUIT+. If the shell is not in the POSIXly-correct mode, the built-in accepts +SIG+-prefixed signal names as well. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_local.txt000066400000000000000000000011611354143602500151550ustar00rootroot00000000000000= Local built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Local built-in The dfn:[local built-in] prints or sets local variables. [[syntax]] == Syntax - +local [-rxX] [{{name}}[={{value}}]...]+ [[description]] == Description The local built-in is equivalent to the link:_typeset.html[typeset built-in] except that the +-f+ (+--functions+) and +-g+ (+--global+) options cannot be used. [[notes]] == Notes The local built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_popd.txt000066400000000000000000000017531354143602500150340ustar00rootroot00000000000000= Popd built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Popd built-in The dfn:[popd built-in] pops a directory from the directory stack. [[syntax]] == Syntax - +popd [{{index}}]+ [[description]] == Description The popd built-in removes the last entry from the link:_dirs.html[directory stack], returning to the previous working directory. If {{index}} is given, the entry specified by {{index}} is removed instead of the last one. [[operands]] == Operands {{index}}:: The index of a directory stack entry you want to remove. + If omitted, `+0` (the last entry) is assumed. [[exitstatus]] == Exit status The exit status of the popd built-in is zero unless there is any error. [[notes]] == Notes It is an error to use this built-in when there is only one directory stack entry. The popd built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_printf.txt000066400000000000000000000162411354143602500153720ustar00rootroot00000000000000= Printf built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Printf built-in The dfn:[printf built-in] prints formatted values. [[syntax]] == Syntax - +printf {{format}} [{{value}}...]+ [[description]] == Description The printf built-in formats {{value}}s according to {{format}} and prints them to the standard output. Unlike the link:_echo.html[echo built-in], the printf built-in does not print a newline automatically. The formatting process is very similar to that of the printf function in the C programming language. You can use conversion specifications (which start with +%+) and escape sequences (which start with +\+) in {{format}}. Any other characters that are not part of a conversion specification or escape sequence are printed literally. [[convspec]] === Conversion specifications A conversion specification starts with a percent sign (+%+). A conversion specification except +%%+ consumes a {{value}}, which is formatted according to the specification and printed. Each conversion specification consumes one {{value}} in the order of appearance. If there are more {{value}}s than conversion specifications, the entire {{format}} is re-processed until all the {{value}}s are consumed. If a {{value}} to be consumed is missing, it is assumed to be an empty string (if the specification requires a string) or zero (if a number). If no {{value}}s are given, {{format}} is processed just once. Available conversion specifications are: +%d+:: +%i+:: prints a signed integer in decimal +%u+:: prints an unsigned integer in decimal +%o+:: prints an unsigned integer in octal +%x+:: prints an unsigned integer in lowercase hexadecimal +%X+:: prints an unsigned integer in uppercase hexadecimal +%f+:: prints a floating-point number in lowercase +%F+:: prints a floating-point number in uppercase +%e+:: prints a floating-point number with exponent in lowercase +%E+:: prints a floating-point number with exponent in uppercase +%g+:: the same as +%f+ or +%e+, automatically selected +%G+:: the same as +%F+ or +%E+, automatically selected +%c+:: prints the first character of string +%s+:: prints a string +%b+:: prints a string (recognizing escape sequences like the link:_echo.html#escapes[echo built-in]) +%%+:: prints a percent sign (+%+) For +%g+ and +%G+, the specification that is actually used is +%f+ or +%F+ if the exponent part is between -5 and the precision (exclusive); +%e+ or +%E+ otherwise. In a conversion specification except +%%+, the leading percent sign may be followed by flags, field width, and/or precision in this order. [[convspec-flags]] ==== Flags The flags are a sequence of any number of the following characters: Minus sign (+-+):: With this flag, spaces are appended to the formatted value to fill up to the field width. Otherwise, spaces are prepended. Plus sign (+++):: A plus or minus sign is always prepended to a number. Space (+ +):: A space is prepended to a formatted number if it has no plus or minus sign. Hash sign (+#+):: The value is formatted in an alternative form: For +%o+, the printed octal integer has at least one leading zero. For +%x+ and +%X+, a non-zero integer is formatted with +0x+ and +0X+ prefixes, respectively. For +%e+, +%E+, +%f+, +%F+, +%g+, and +%G+, a decimal mark (a.k.a. radix character) is always printed even if the value is an exact integer. For +%g+ and +%G+, the printed number has at least one digit in the fractional part. Zero (+0+):: Zeros are prepended to a formatted number to fill up to the field width. This flag is ignored if the minus flag is specified or if the conversion specification is +%d+, +%i+, +%u+, +%o+, +%x+, or +%X+ with a precision. [[convspec-width]] ==== Field width A field width is specified as a decimal integer that has no leading zeros. A field width defines a minimum byte count of a formatted value. If the formatted value does not reach the minimum byte count, so many spaces are prepended that the printed value has the specified byte count. [[convspec-precision]] ==== Precision A precision is specified as a period (+.+) followed by a decimal integer. If the integer is omitted after the period, the precision is assumed to be zero. For conversion specifications +%d+, +%i+, +%u+, +%o+, +%x+, and +%X+, a precision defines a minimum digit count. If the formatted integer does not reach the minimum digit count, so many zeros are prepended that the printed integer has the specified number of digits. The default precision is one for these conversion specifications. For conversion specifications +%e+, +%E+, +%f+, and +%F+, a precision defines the number of digits after the decimal mark. The default precision is six for these conversion specifications. For conversion specifications +%g+, and +%G+, a precision defines a maximum number of significant digits in the printed value. The default precision is six for these conversion specifications. For conversion specifications +%s+, and +%b+, a precision defines a maximum byte count of the printed string. The default precision is infinity for these conversion specifications. [[convspec-examples]] ==== Examples In the conversion specification +%08.3f+, the zero flag is specified, the field width is 8, and the precision is 3. If this specification is applied to value 12.34, the output will be +0012.340+. [[escapes]] === Escape sequences The following escape sequences are recognized in {{format}}: +\a+:: Bell character (ASCII code: 7) +\b+:: Backspace (ASCII code: 8) +\f+:: Form feed character (ASCII code: 12) +\n+:: Newline character (ASCII code: 10) +\r+:: Carriage return character (ASCII code: 13) +\t+:: Horizontal tab character (ASCII code: 9) +\v+:: Vertical tab character (ASCII code: 11) +\\+:: Backslash +\"+:: Double quotation +\'+:: Single quotation (apostrophe) +\{{xxx}}+:: Character whose code is {{xxx}}, where {{xxx}} is an octal number of at most three digits. [[operands]] == Operands {{format}}:: A string that defines how {{value}}s should be formatted. {{value}}s:: Values that are formatted according to {{format}}. + A value is either a number or a string. + When a numeric value is required, {{value}} can be a single or double quotation followed by a character, instead of a normal number. For example, the command `printf '%d' '"3'` will print +51+ on a typical environment where character +3+ has character code 51. [[exitstatus]] == Exit status The exit status of the printf built-in is zero unless there is any error. [[notes]] == Notes The POSIX standard does not precisely define how multibyte characters should be handled by the built-in. When you use the +%s+ conversion specification with precision or the +%c+ conversion specification, you may obtain unexpected results if the formatted value contains a character that is represented by more than one byte. Yash never prints only part of the bytes that represent a single multibyte character because all multibyte characters are converted to wide characters when processed in the shell. If the shell is not in the link:posix.html[POSIXly-correct mode] and the ``long double'' floating-point arithmetic is supported on the running system, then ``long double'' is used for floating-point conversion specifications. Otherwise, ``double'' is used. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_pushd.txt000066400000000000000000000034501354143602500152110ustar00rootroot00000000000000= Pushd built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Pushd built-in The dfn:[pushd built-in] pushes a directory into the directory stack. [[syntax]] == Syntax - +pushd [-L|-P] [{{directory}}]+ [[description]] == Description The pushd built-in changes the working directory to {{directory}} in the same manner as the link:_cd.html[cd built-in] and adds it to the directory stack. If the working directory could not be changed successfully, the stack is not modified. [[options]] == Options The pushd built-in accepts the following option as well as the link:_cd.html#options[options that can be used for the cd built-in]: +--remove-duplicates+:: If the new working directory has already been in the directory stack, the existing entry is removed from the stack before the new directory is pushed into the stack. [[operands]] == Operands {{directory}}:: The pathname of the new working directory. + If {{directory}} is a single hyphen (`-'), the value of the link:params.html#sv-oldpwd[+OLDPWD+ variable] is assumed for the new directory pathname, which is printed to the standard output. + If {{directory}} is an integer with a plus or minus sign, it is considered as an entry index of the directory stack. The entry is removed from the stack and then pushed to the stack again. + If {{directory}} is omitted, the working directory is changed to the directory specified by the +--default-directory=...+ option. If that option is not specified either, the default is index `+1`. [[exitstatus]] == Exit status The exit status of the pushd built-in is zero unless there is any error. [[notes]] == Notes The pushd built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_pwd.txt000066400000000000000000000022421354143602500146560ustar00rootroot00000000000000= Pwd built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Pwd built-in The dfn:[pwd built-in] prints the current working directory. [[syntax]] == Syntax - +pwd [-L|-P]+ [[description]] == Description The pwd built-in prints an absolute path to the shell's current working directory to the standard output. [[options]] == Options +-L+:: +--logical+:: If the value of the link:params.html#sv-pwd[+PWD+ variable] is an absolute path to the shell's working directory and the path does not contain any +.+ or +..+ components, then the path is printed. Otherwise, the printed path is the same as when the +-P+ option is specified. +-P+:: +--physical+:: The printed path does not contain any +.+ or +..+ components, symbolic link components, or redundant slashes. The +-L+ (+--logical+) and +-P+ (+--physical+) options are mutually exclusive: only the last specified one is effective. If neither is specified, +-L+ is assumed. [[exitstatus]] == Exit status The exit status of the pwd built-in is zero unless there is any error. [[notes]] == Notes The pwd built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_read.txt000066400000000000000000000056401354143602500150040ustar00rootroot00000000000000= Read built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Read built-in The dfn:[read built-in] reads a line from the standard input. [[syntax]] == Syntax - +read [-Aer] [-P|-p] {{variable}}...+ [[description]] == Description The read built-in reads a line of string from the standard input and assigns it to the specified link:params.html#variables[variables]. If the +-r+ (+--raw-mode+) option is specified, all characters in the line are treated literally. If the +-r+ (+--raw-mode+) option is not specified, backslashes in the line are treated as link:syntax.html#quotes[quotations]. If a backslash is at the end of the line, it is treated as a line continuation. When the built-in reads the next line, the link:params.html#sv-ps2[+PS2+ variable] is used as a prompt if the shell is link:interact.html[interactive] and the standard input is a terminal. The input line is subject to link:expand.html#split[field splitting]. The resulting words are assigned to {{variable}}s in order. If there are more words than {{variable}}s, the last variable is assigned all the remaining words (as if the words were not split). If the words are fewer than {{variable}}s, the remaining variables are assigned empty strings. [[options]] == Options +-A+:: +--array+:: Make the last {{variable}} an link:params.html#arrays[array]. Instead of assigning a concatenation of the remaining words to a normal variable, the words are assigned to an array. +-e+:: +--line-editing+:: Use link:lineedit.html[line-editing] to read the line. + To use line-editing, all of the following conditions must also be met: + - The shell is link:interact.html[interactive]. - The link:_set.html#so-vi[vi] or link:_set.html#so-emacs[emacs] option is enabled. - The standard input and standard error are connected to a terminal. +-P+:: +--ps1+:: Print the link:params.html#sv-ps1[+PS1+ variable] as a prompt before reading the (first) line if the shell is interactive and the standard input is a terminal. +-p {{prompt}}+:: +--prompt={{prompt}}+:: Print the specified {{prompt}} before reading the (first) line if the shell is interactive and the standard input is a terminal. +-r+:: +--raw-mode+:: Don't treat backslashes as quotations. [[operands]] == Operands {{variable}}s:: Names of variables to which input words are assigned. [[exitstatus]] == Exit status The exit status of the read built-in is zero unless there is any error. Note that the exit status is non-zero if an end of input is encountered before reading the entire line. [[notes]] == Notes The read built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard defines the +-r+ option only: other options cannot be used in the link:posix.html[POSIXly-correct mode]. The link:params.html#sv-ps1r[+PS1R+] and link:params.html#sv-ps1s[+PS1S+] variables affect the behavior of line-editing if the +PS1+ prompt is used. The same for +PS2+. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_readonly.txt000066400000000000000000000013361354143602500157040ustar00rootroot00000000000000= Readonly built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Readonly built-in The dfn:[readonly built-in] makes variables and functions read-only. [[syntax]] == Syntax - +readonly [-pxX] [{{name}}[={{value}}]...]+ - +readonly -f[p] [{{name}}...]+ [[description]] == Description The readonly built-in is equivalent to the link:_typeset.html[typeset built-in] with the +-gr+ option. [[notes]] == Notes The readonly built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines the +-p+ option only; other options cannot be used in the link:posix.html[POSIXly-correct mode]. The POSIX does not allow using the option together with operands. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_return.txt000066400000000000000000000042211354143602500154020ustar00rootroot00000000000000= Return built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Return built-in The dfn:[return built-in] returns from a function or script. [[syntax]] == Syntax - +return [-n] [{{exit_status}}]+ [[description]] == Description When executed without the +-n+ (+--no-return+) option, one of the following happens: - If the shell is executing a link:exec.html#function[function], the execution of the function is terminated. - If the link:_dot.html[dot built-in] is executing a script, the execution of the script is terminated. - If the shell is executing a script during link:invoke.html#init[initialization], the execution of the script is terminated. - If the shell is executing a link:_trap.html[trap], the execution of the trap is terminated for the currently handled signal. - Otherwise, the shell exits unless it is link:interact.html[interactive]. When executed with the +-n+ (+--no-return+) option, the built-in does nothing but return the specified {{exit_status}}. [[options]] == Options +-n+:: +--no-return+:: Do not terminate a function, script, trap, or the shell. [[operands]] == Operands {{exit_status}}:: The exit status of the built-in. + The value must be a non-negative integer. + If omitted, the exit status of the last executed command is used. (But when the shell is executing a link:_trap.html[trap], the exit status of the last command before the trap is used.) [[exitstatus]] == Exit status The exit status of the return built-in is defined by the {{exit_status}} operand. The exit status is used also as the exit status of the terminated function, script, or the shell. [[notes]] == Notes The return built-in is a link:builtin.html#types[special built-in]. The POSIX standard provides that the {{exit_status}} operand should be between 0 and 255 (inclusive). Yash accepts integers larger than 255 as an extension. In the POSIX standard, the behavior of the return built-in is defined only when the shell is executing a function or script. The POSIX standard defines no options for the return built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_set.txt000066400000000000000000000233011354143602500146560ustar00rootroot00000000000000= Set built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Set built-in The dfn:[set built-in] sets shell options and positional parameters. [[syntax]] == Syntax - +set [{{option}}s] [{{operand}}s]+ - +set -o+ - +set +o+ The set built-in requires that all options precede operands. Any command line arguments after the first operand are all treated as operands. [[description]] == Description When executed without any command arguments, the built-in prints a list of all existing link:params.html#variables[variables] to the standard input in a form that can be reused as commands that will restore the variable definitions. When +-o+ is the only command argument, the built-in prints a list of shell options with their current settings. When `+o` is the only command argument, the built-in prints commands that can be reused to restore the current shell option settings. In other cases, the built-in changes shell option settings and/or link:params.html#positional[positional parameters]. [[options]] == Options When one or more options are specified, the built-in enables or disables the shell options. A normal hyphen-prefixed option enables a shell option. An option that is prefixed with a plus (`+`) instead of a hyphen disables a shell option. For example, options +-m+, +-o monitor+, and +--monitor+ enable the monitor option and options `+m`, `+o monitor`, `++monitor` disable it. The name of a long option is case-insensitive and may include irrelevant non-alphanumeric characters, which are ignored. For example, options +--le-comp-debug+ and +--LeCompDebug+ are equivalent. If +no+ is prepended to the name of a long option, the meaning is reversed. For example, +--noallexport+ is equivalent to `++allexport` and `++nonotify` to +--notify+. An option can be specified in one of the following forms: - a long option e.g. +--allexport+ - an +-o+ option with a option name specified as the argument e.g. +-o allexport+ - a single-character option e.g. +-a+ Not all options can be specified as single-character options. The available options are: [[so-allexport]]all-export (+-a+):: When enabled, all link:params.html#variables[variables] are automatically link:params.html#variables[exported] when assigned. [[so-braceexpand]]brace-expand:: This option enables link:expand.html#brace[brace expansion]. [[so-caseglob]]case-glob:: (Enabled by default) When enabled, pattern matching is case-sensitive in link:expand.html#glob[pathname expansion]. [[so-clobber]]clobber (`+C`):: (Enabled by default) When enabled, the +>+ link:redir.html#file[redirection] behaves the same as the +>|+ redirection. [[so-curasync]]cur-async:: [[so-curbg]]cur-bg:: [[so-curstop]]cur-stop:: (Enabled by default) These options affect choice of the current job (cf. link:job.html#jobid[job ID]). [[so-dotglob]]dot-glob:: When enabled, periods at the beginning of filenames are not treated specially in link:expand.html#glob[pathname expansion]. [[so-emacs]]emacs:: This option enables link:lineedit.html[line-editing] in the link:lineedit.html#modes[emacs mode]. [[so-emptylastfield]]empty-last-field:: When enabled, link:expand.html#split[field splitting] does not remove the last field even if it is empty. [[so-errexit]]err-exit (+-e+):: When enabled, if a link:syntax.html#pipelines[pipeline] ends with a non-zero exit status, the shell immediately exits unless the following suppress condition is met: - the pipeline is a condition of an link:syntax.html#if[if command] or link:syntax.html#while-until[while or until loop]; - the pipeline is prefixed by +!+; or - the pipeline is a single link:syntax.html#compound[compound command] other than a subshell link:syntax.html#grouping[grouping]. [[so-errreturn]]err-return:: This option is like the err-exit option, but the link:_return.html[return built-in] is executed instead of the shell exiting on a non-zero exit status. Unlike err-exit, the suppress condition does not apply inside a link:exec.html#function[function], subshell link:syntax.html#grouping[grouping], or link:_dot.html[script file]. [[so-exec]]exec (`+n`):: (Enabled by default) Commands are actually executed only when this option is enabled. Otherwise, commands are just parsed and not executed. Disabling this option may be useful for syntax checking. In an link:interact.html[interactive shell], this option is always assumed enabled. [[so-extendedglob]]extended-glob:: This option enables link:expand.html#extendedglob[extension in pathname expansion]. [[so-forlocal]]for-local:: (Enabled by default) If a link:syntax.html#for[for loop] is executed within a link:exec.html#function[function], this option causes the iteration variable to be created as a link:exec.html#localvar[local variable], even if the variable already exists globally. This option has no effect if the link:posix.html[POSIXly-correct mode] is active. [[so-glob]]glob (`+f`):: (Enabled by default) This option enables link:expand.html#glob[pathname expansion]. [[so-hashondef]]hash-on-def (+-h+):: When a link:exec.html#function[function] is defined when this option is enabled, the shell immediately performs link:exec.html#search[command path search] for each command that appears in the function and caches the command's full path. [[so-histspace]]hist-space:: When enabled, command lines that start with a whitespace are not saved in link:interact.html#history[command history]. [[so-ignoreeof]]ignore-eof:: When enabled, an link:interact.html[interactive shell] does not exit when EOF (end of file) is input. This prevents the shell from exiting when you accidentally hit Ctrl-D. [[so-lealwaysrp]]le-always-rp:: [[so-lecompdebug]]le-comp-debug:: [[so-leconvmeta]]le-conv-meta:: [[so-lenoconvmeta]]le-no-conv-meta:: [[so-lepredict]]le-predict:: [[so-lepredictempty]]le-predict-empty:: [[so-lepromptsp]]le-prompt-sp:: [[so-levisiblebell]]le-visible-bell:: See link:lineedit.html#options[shell options on line-editing]. [[so-markdirs]]mark-dirs:: When enabled, resulting directory names are suffixed by a slash in link:expand.html#glob[pathname expansion]. [[so-monitor]]monitor (+-m+):: This option enables link:job.html[job control]. This option is enabled by default for an link:interact.html[interactive shell]. [[so-notify]]notify (+-b+):: When the status of a link:job.html[job] changes when this option is enabled, the shell immediately notifies at any time. This option overrides the notify-le option. [[so-notifyle]]notify-le:: This option is similar to the notify option, but the status change is notified only while the shell is waiting for input with link:lineedit.html[line-editing]. [[so-nullglob]]null-glob:: When enabled, in link:expand.html#glob[pathname expansion], patterns that do not match any pathname are removed from the command line rather than left as is. [[so-pipefail]]pipe-fail:: When enabled, the exit status of a link:syntax.html#pipelines[pipeline] is zero if and only if all the subcommands of the pipeline exit with an exit status of zero. [[so-posixlycorrect]]posixly-correct:: This option enables the link:posix.html[POSIXly-correct mode]. [[so-traceall]]trace-all:: (Enabled by default) When this option is disabled, the <> is temporarily disabled while the shell is executing commands defined in the link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+], link:params.html#sv-prompt_command[+PROMPT_COMMAND+], or link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+] variable. [[so-unset]]unset (`+u`):: (Enabled by default) When enabled, an undefined parameter is expanded to an empty string in link:expand.html#params[parameter expansion] and treated as zero in link:expand.html#arith[arithmetic expansion]. When disabled, expansion of an undefined parameter results in an error. [[so-verbose]]verbose (+-v+):: When enabled, the shell prints each command line to the standard error before parsing and executing it. [[so-vi]]vi:: This option enables link:lineedit.html[line-editing] in the link:lineedit.html#modes[vi mode]. This option is enabled by default in an link:interact.html[interactive shell] if the standard input and error are both terminals. [[so-xtrace]]x-trace (+-x+):: When enabled, the results of link:expand.html[expansion] are printed to the standard error for each link:syntax.html#simple[simple command] being executed. When printed, each line is prepended with an expansion result of the link:params.html#sv-ps4[+PS4+ variable]. See also the <>. [[operands]] == Operands If one or more operands are passed to the set built-in, current link:params.html#positional[positional parameters] are all removed and the operands are set as new positional parameters. If the +--+ separator (cf. link:builtin.html#argsyntax[syntax of command arguments]) is passed, the positional parameters are set even when there are no operands, in which case new positional parameters will be nothing. [[exitstatus]] == Exit status The exit status of the set built-in is zero unless there is any error. [[notes]] == Notes The set built-in is a link:builtin.html#types[special built-in]. In the POSIX standard, available shell options are much limited. The standard does not define: - long options such as +--allexport+, - prepending +no+ to negate an option, - using uppercase letters and/or non-alphanumeric characters in option names The options defined in the standard are: - +-a+, +-o allexport+ - +-e+, +-o errexit+ - +-m+, +-o monitor+ - +-C+, +-o noclobber+ - +-n+, +-o noexec+ - +-f+, +-o noglob+ - +-b+, +-o notify+ - +-u+, +-o nounset+ - +-v+, +-o verbose+ - +-x+, +-o xtrace+ - +-h+ - +-o ignoreeof+ - +-o nolog+ - +-o vi+ Yash does not support the nolog option, which prevents link:syntax.html#funcdef[function definitions] from being added to link:interact.html#history[command history]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_shift.txt000066400000000000000000000031431354143602500152020ustar00rootroot00000000000000= Shift built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Shift built-in The dfn:[shift built-in] removes some link:params.html#positional[positional parameters] or link:params.html#arrays[array] values. [[syntax]] == Syntax - +shift [-A {{array}}] [{{count}}]+ [[description]] == Description The shift built-in removes the first {{count}} link:params.html#positional[positional parameters] or link:params.html#arrays[array] values, where {{count}} is specified by the operand. [[options]] == Options +-A {{array}}+:: +--array={{array}}+:: Remove first {{count}} values of {{array}} instead of positional parameters. [[operands]] == Operands {{count}}:: The number of positional parameters or array values to be removed. + It is an error if the actual number of positional parameters or array values is less than {{count}}. If omitted, the default value is one. If negative, the last -{{count}} positional parameters or array values are removed instead of the first ones. [[exitstatus]] == Exit status The exit status of the shift built-in is zero unless there is any error. [[notes]] == Notes The shift built-in is a link:builtin.html#types[special built-in]. The number of positional parameters can be obtained with the link:params.html#sp-hash[+#+ special parameter]. The number of array values can be obtained with +${{{array}}[#]}+. The POSIX standard defines no options for the shift built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. Negative operands are not allowed in the POSIXly-correct mode. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_suspend.txt000066400000000000000000000024251354143602500155500ustar00rootroot00000000000000= Suspend built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Suspend built-in The dfn:[suspend built-in] suspends the shell. [[syntax]] == Syntax - +suspend [-f]+ [[description]] == Description The suspend built-in sends a SIGSTOP signal to all processes in the process group to which the shell process belongs. The signal suspends the processes (including the shell). The suspended processes resume when they receive a SIGCONT signal. If the shell is link:interact.html[interactive] and its process group ID is equal to the process ID of the session leader, the shell prints a warning message and refuses to send a signal unless the +-f+ (+--force+) option is specified. (In such a case, there is no other job-controlling shell that can send a SIGCONT signal to resume the suspended shell, so the shell could never be resumed.) [[options]] == Options +-f+:: +--force+:: Suppress warnings that would prevent the shell from sending a signal. [[exitstatus]] == Exit status The exit status is zero if the signal was successfully sent and non-zero otherwise. [[notes]] == Notes The suspend built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_test.txt000066400000000000000000000133431354143602500150470ustar00rootroot00000000000000= Test built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Test built-in The dfn:[test built-in] evaluates an expression. [[syntax]] == Syntax - +test {{expression}}+ - +[ {{expression}} ]+ The test built-in does not distinguish options and operands; all command line arguments are interpreted as {{expression}}. If the built-in is executed with the name +[+, {{expression}} must be followed by +]+. [[description]] == Description The test built-in evaluates {{expression}} as a conditional expression that is made up of operators and operands described below. The exit status is 0 if the condition is true and 1 otherwise. The unary operators below test a file. If the operand {{file}} is a symbolic link, the file referred to by the link is tested (except for the +-h+ and +-L+ operators). +-b {{file}}+:: {{file}} is a block special file +-c {{file}}+:: {{file}} is a character special file +-d {{file}}+:: {{file}} is a directory +-e {{file}}+:: {{file}} exists +-f {{file}}+:: {{file}} is a regular file +-G {{file}}+:: {{file}}'s group ID is same as the shell's effective group ID +-g {{file}}+:: {{file}}'s set-group-ID flag is set +-h {{file}}+:: same as -L +-k {{file}}+:: {{file}}'s sticky bit is set +-L {{file}}+:: {{file}} is a symbolic link +-N {{file}}+:: {{file}} has not been accessed since last modified +-O {{file}}+:: {{file}}'s user ID is same as the shell's effective user ID +-p {{file}}+:: {{file}} is a FIFO (named pipe) +-r {{file}}+:: {{file}} is readable +-S {{file}}+:: {{file}} is a socket +-s {{file}}+:: {{file}} is not empty +-u {{file}}+:: {{file}}'s set-user-ID flag is set +-w {{file}}+:: {{file}} is writable +-x {{file}}+:: {{file}} is executable The unary operator below tests a file descriptor: +-t {{fd}}+:: {{fd}} is associated with a terminal The unary operators below test a string: +-n {{string}}+:: {{string}} is not empty +-z {{string}}+:: {{string}} is empty The unary operator below tests a link:_set.html[shell option]: +-o ?{{option}}+:: {{option}} is a valid shell option name +-o {{option}}+:: {{option}} is a valid shell option name that is enabled The binary operators below compare files. Non-existing files are considered older than any existing files. +{{file1}} -nt {{file2}}+:: {{file1}} is newer than {{file2}} +{{file1}} -ot {{file2}}+:: {{file1}} is older than {{file2}} +{{file1}} -ef {{file2}}+:: {{file1}} is a hard link to {{file2}} The binary operators below compare strings: +{{string1}} = {{string2}}+:: +{{string1}} == {{string2}}+:: {{string1}} is the same string as {{string2}} +{{string1}} != {{string2}}+:: {{string1}} is not the same string as {{string2}} The binary operators below compare strings according to the alphabetic order in the current locale: +{{string1}} === {{string2}}+:: {{string1}} is equal to {{string2}} +{{string1}} !== {{string2}}+:: {{string1}} is not equal to {{string2}} +{{string1}} < {{string2}}+:: {{string1}} is less than {{string2}} +{{string1}} <= {{string2}}+:: {{string1}} is less than or equal to {{string2}} +{{string1}} > {{string2}}+:: {{string1}} is greater than {{string2}} +{{string1}} >= {{string2}}+:: {{string1}} is greater than or equal to {{string2}} The binary operator below performs pattern matching: +{{string}} =~ {{pattern}}+:: extended regular expression {{pattern}} matches (part of) {{string}} The binary operators below compare integers: +{{v1}} -eq {{v2}}+:: {{v1}} is equal to {{v2}} +{{v1}} -ne {{v2}}+:: {{v1}} is not equal to {{v2}} +{{v1}} -gt {{v2}}+:: {{v1}} is greater than {{v2}} +{{v1}} -ge {{v2}}+:: {{v1}} is greater than or equal to {{v2}} +{{v1}} -lt {{v2}}+:: {{v1}} is less than {{v2}} +{{v1}} -le {{v2}}+:: {{v1}} is less than or equal to {{v2}} The binary operators below compare version numbers: +{{v1}} -veq {{v2}}+:: {{v1}} is equal to {{v2}} +{{v1}} -vne {{v2}}+:: {{v1}} is not equal to {{v2}} +{{v1}} -vgt {{v2}}+:: {{v1}} is greater than {{v2}} +{{v1}} -vge {{v2}}+:: {{v1}} is greater than or equal to {{v2}} +{{v1}} -vlt {{v2}}+:: {{v1}} is less than {{v2}} +{{v1}} -vle {{v2}}+:: {{v1}} is less than or equal to {{v2}} The operators below can be used to make complex expressions: +! {{expression}}+:: negate (reverse) the result +( {{expression}} )+:: change operator precedence +{{expression1}} -a {{expression2}}+:: logical conjunction (and) +{{expression1}} -o {{expression2}}+:: logical disjunction (or) If the expression is a single word without operators, the +-n+ operator is assumed. An empty expression evaluates to false. [[version-compare]] === Comparison of version numbers Comparison of version numbers is similar to comparison of strings in alphabetic order. The differences are: - Adjacent digits are treated as an integer. Integers are compared in mathematical order rather than alphabetic order. - Digits are considered larger than any non-digit characters. For example, version numbers +0.1.2-3+ and +00.001.02-3+ are equal and +0.2.1+ is smaller than +0.10.0+. [[exitstatus]] == Exit status The exit status of the test built-in is 0 if {{expression}} is true and 1 otherwise. The exit status is 2 if {{expression}} cannot be evaluated because of a syntax error or any other reasons. [[notes]] == Notes Complex expressions may cause confusion and should be avoided. Use the shell's link:syntax.html#compound[compound commands]. For example, +[ 1 -eq 1 ] && [ -t = 1 ] && ! [ foo ]+ is preferred over +[ 1 -eq 1 -a -t = 1 -a ! foo ]+. The POSIX standard provides that the exit status should be larger than 1 on error. The POSIX standard does not define the following operators: +-G+, +-k+, +-N+, +-O+, +-nt+, +-ot+, +-ef+, +==+, +===+, +!==+, +<+, +<=+, +>+, +>=+, +=~+, +-veq+, +-vne+, +-vgt+, +-vge+, +-vlt+, and +-vle+. POSIX neither specifies +-o+ as a unary operator. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_times.txt000066400000000000000000000014571354143602500152140ustar00rootroot00000000000000= Times built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Times built-in The dfn:[times built-in] prints CPU time usage. [[syntax]] == Syntax - +times+ [[description]] == Description The times built-in prints the CPU times consumed by the shell process and its child processes to the standard output. The built-in prints two lines: the first line shows the CPU time of the shell process and the second one that of its child processes (not including those which have not terminated). Each line shows the CPU times consumed in the user and system mode. [[exitstatus]] == Exit status The exit status of the times built-in is zero unless there is any error. [[notes]] == Notes The times built-in is a link:builtin.html#types[special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_trap.txt000066400000000000000000000076631354143602500150460ustar00rootroot00000000000000= Trap built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Trap built-in The dfn:[trap built-in] sets or prints signal handlers. [[syntax]] == Syntax - +trap+ - +trap {{action}} {{signal}}...+ - +trap {{signal_number}} [{{signal}}...]+ - +trap -p [{{signal}}...]+ [[description]] == Description The trap built-in sets or prints actions that are taken when the shell receives signals. (Those actions are called dfn:[traps].) When executed with {{action}} and one or more {{signal}}s, the built-in sets the traps for {{signal}}s to {{action}}. If the shell receives one of the signals, the action will be taken. If the first operand is {{signal_number}} instead of {{action}}, the built-in resets the traps for {{signal_number}} and {{signal}}s as if {{action}} was +-+. When executed with the +-p+ (+--print+) option or with no operands, the built-in prints currently set traps to the standard output in a format that can be executed as commands that restore the current traps. If one or more {{signal}}s are specified, only those signals are printed. Otherwise, all signals with non-default actions are printed. (In some situations, however, the built-in may print previous trap settings instead of the current. See notes below.) [[options]] == Options +-p+:: +--print+:: Print current trap settings. [[operands]] == Operands {{action}}:: An action that will be taken when {{signal}} is received. + If {{action}} is a single hyphen (+-+), the action is reset to the default action that is defined by the operating system. If {{action}} is an empty string, the signal is ignored on receipt. Otherwise, {{action}} is treated as a command string: the string is parsed and executed as commands when the signal is received. (If a signal is received while a command is being executed, the action is taken just after the command finishes.) {{signal}}:: The number or name of a signal. + If {{signal}} is number +0+ or name +EXIT+, it is treated as a special imaginary signal that is always received when the shell exits. The action set for this signal is taken when the shell exits normally. {{signal_number}}:: This is like {{signal}}, but must be a number. [[exitstatus]] == Exit status The exit status of the trap built-in is zero unless there is any error. [[notes]] == Notes The trap built-in is a link:builtin.html#types[special built-in]. The POSIX standard defines no options for the trap built-in; the built-in accepts no options in the link:posix.html[POSIXly-correct mode]. The POSIX standard requires that signal names must be specified without the +SIG+-prefix, like +INT+ and +QUIT+. As an extension, yash accepts +SIG+-prefixed names like +SIGINT+ and +SIGQUIT+ and treats signal names case-insensitively. === Reusing output of the built-in Output of the trap built-in can be saved in a variable, which can be later executed by the link:_eval.html[eval built-in] to restore the traps. ---- saved_traps=$(trap) trap '...' INT eval "$saved_traps" ---- There are some tricks behind the scenes to allow this idiom. You use a link:expand.html#cmdsub[command substitution] to save the output of the trap built-in in the variable. The command substitution is executed in a link:exec.html#subshell[subshell]. The subshell resets all traps (except ignored ones) at the beginning of itself. This seemingly would result in (almost) empty output from the built-in that would fail to restore the traps as expected. To avoid that pitfall, POSIX requires the shell to follow one of the two options below: - If a command substitution just contains a single trap built-in, traps should not be reset when the subshell is started to execute the built-in; or - A subshell always resets the traps but remembers the previous traps. If the trap built-in is executed in the subshell but no other trap built-in has been executed to modify traps in the subshell, then the built-in should print the remembered traps. Yash obeys the second. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_true.txt000066400000000000000000000011631354143602500150440ustar00rootroot00000000000000= True built-in :encoding: UTF-8 :lang: en //:title: Yash manual - True built-in The dfn:[true built-in] does nothing successfully. [[syntax]] == Syntax - +true+ [[description]] == Description The true built-in does nothing. Any command line arguments are ignored. [[exitstatus]] == Exit status The exit status of the true built-in is zero. [[notes]] == Notes The true built-in is a link:builtin.html#types[semi-special built-in]. The true and link:_colon.html[colon] built-ins have the same effect, but true is a semi-special built-in while colon is a special. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_type.txt000066400000000000000000000013101354143602500150400ustar00rootroot00000000000000= Type built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Type built-in The dfn:[type built-in] identifies a command. [[syntax]] == Syntax - +type [-abefkp] [{{command}}...]+ [[description]] == Description The type built-in is equivalent to the link:_command.html[command built-in] with the +-V+ option. [[notes]] == Notes The POSIX standard does not define the relation between the type and command built-ins. The standard does not define options for the type built-in. At least one {{command}} operand must be specified in the link:posix.html[POSIXly-correct mode]. The type built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_typeset.txt000066400000000000000000000064031354143602500155640ustar00rootroot00000000000000= Typeset built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Typeset built-in The dfn:[typeset built-in] prints or sets variables or functions. [[syntax]] == Syntax - +typeset [-gprxX] [{{variable}}[={{value}}]...]+ - +typeset -f[pr] [{{function}}...]+ [[description]] == Description If executed without the +-f+ (+--functions+) option, the typeset built-in prints or sets link:params.html#variables[variables] to the standard output. Otherwise, it prints or sets link:exec.html#function[functions]. If executed with the +-p+ (+--print+) option, the built-in prints the variables or functions specified by operands. Without the option, it sets variables or functions. If no operands are specified, it prints all existing variables or functions, regardless of whether the +-p+ (+--print+) option is specified. [[options]] == Options +-f+:: +--functions+:: Print or set functions rather than variables. +-g+:: +--global+:: When setting a new variable, the variable will be a global variable if this option is specified. Without this option, the variable would be a link:exec.html#localvar[local variable]. + When printing variables, all existing variables including global variables are printed if this option is specified. Without this option, only local variables are printed. +-p+:: +--print+:: Print variables or functions in a form that can be parsed and executed as commands that will restore the currently set variables or functions. +-r+:: +--readonly+:: When setting variables or functions, make them read-only. + When printing variables or functions, print read-only variables or functions only. +-x+:: +--export+:: When setting variables, link:params.html#variables[mark them for export], so that they will be exported to external commands. + When printing variables, print exported variables only. +-X+:: +--unexport+:: When setting variables, cancel exportation of the variables. [[operands]] == Operands {{variable}} (without {{value}}):: The name of a variable that is to be set or printed. + Without the +-p+ (+--print+) option, the variable is defined (if not yet defined) but its value is not set nor changed. Variables that are defined without values are treated as unset in link:expand.html#params[parameter expansion]. {{variable}}={{value}}:: The name of a variable and its new value. + The value is assigned to the variable (regardless of the +-p+ (+--print+) option). {{function}}:: The name of an existing function that is to be set or printed. [[exitstatus]] == Exit status The exit status of the typeset built-in is zero unless there is any error. [[notes]] == Notes A global variable cannot be newly defined if a local variable has already been defined with the same name. The local variable will be set regardless of the +-g+ (+--global+) option. The typeset built-in is a link:builtin.html#types[semi-special built-in]. In the POSIX standard, it is defined as a command with unspecified behavior. The link:_export.html[export] and link:_readonly.html[readonly] built-ins are equivalent to the typeset built-in with the +-gx+ and +-gr+ options, respectively. The link:_local.html[local built-in] is equivalent to the typeset built-in except that the +-f+ (+--functions+) and +-g+ (+--global+) options cannot be used. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_ulimit.txt000066400000000000000000000061131354143602500153700ustar00rootroot00000000000000= Ulimit built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Ulimit built-in The dfn:[ulimit built-in] sets or prints a resource limit. [[syntax]] == Syntax - +ulimit -a [-H|-S]+ - +ulimit [-H|-S] [-efilnqrstuvx] [{{limit}}]+ [[description]] == Description The ulimit built-in sets or prints a resource limit. If executed with the +-a+ (+--all+) option, the built-in prints the current limits for all resource types. Otherwise, it sets or prints the limit of a single resource type. The resource type can be specified by the options listed below. The resource limits will affect the current shell process and all commands invoked from the shell. Each resource type has two limit values: the hard and soft limit. You can change a soft limit freely as long as it does not exceed the hard limit. You can decrease a hard limit but cannot increase it without a proper permission. When the +-H+ (+--hard+) or +-S+ (+--soft+) option is specified, the built-in sets or prints the hard or soft limit, respectively. If neither of the options is specified, the built-in sets both the hard and soft limit or prints the soft limit. [[options]] == Options +-H+:: +--hard+:: Set or print a hard limit. +-S+:: +--soft+:: Set or print a soft limit. +-a+:: +--all+:: Print all current limit settings. The following options specify the type of resources. If none of them is specified, +-f+ is the default. The types of resources that can be set depend on the operating system. +-c+:: +--core+:: Maximum size of core files created (in 512-byte blocks) +-d+:: +--data+:: Maximum size of a process's data segment (in kilobytes) +-e+:: +--nice+:: Maximum scheduling priority (`nice') +-f+:: +--fsize+:: Maximum size of files created by a process (in 512-byte blocks) +-i+:: +--sigpending+:: Maximum number of pending signals +-l+:: +--memlock+:: Maximum memory size that can be locked into RAM (in kilobytes) +-m+:: +--rss+:: Maximum size of a process's resident set (in kilobytes) +-n+:: +--nofile+:: Maximum file descriptor + 1 +-q+:: +--msgqueue+:: Maximum size of POSIX message queues +-r+:: +--rtprio+:: Maximum real-time scheduling priority +-s+:: +--stack+:: Maximum size of a process's stack (in kilobytes) +-t+:: +--cpu+:: Maximum CPU time that can be used by a process (in seconds) +-u+:: +--nproc+:: Maximum number of processes for a user +-v+:: +--as+:: Maximum size of memory used by a process (in kilobytes) +-x+:: +--locks+:: Maximum number of file locks [[operands]] == Operands {{limit}}:: A limit to be set. + The value must be a non-negative integer or one of `hard`, `soft`, and `unlimited`. If {{value}} is `hard` or `soft`, the new limit is set to the current hard or soft limit. If {{limit}} is not specified, the current limit is printed. [[exitstatus]] == Exit status The exit status of the ulimit built-in is zero unless there is any error. [[notes]] == Notes The POSIX standard defines no options other than +-f+. It neither defines `hard`, `soft`, or `unlimited` for {{limit}} values. The ulimit built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_umask.txt000066400000000000000000000054031354143602500152060ustar00rootroot00000000000000= Umask built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Umask built-in The dfn:[umask built-in] sets or prints the file mode creation mask. [[syntax]] == Syntax - +umask {{mask}}+ - +umask [-S]+ [[description]] == Description If executed without the {{mask}} operand, the built-in prints the current file mode creation mask of the shell to the standard output in a form that can later be used as {{mask}} to restore the current mask. Otherwise, the built-in sets the file mode creation mask to {{mask}}. [[options]] == Options +-S+:: +--symbolic+:: Print in the symbolic form instead of the octal integer form. [[operands]] == Operands {{mask}}:: The new file mode creation mask either in the symbolic or octal integer form. === Octal integer form In the octal integer form, the mask is specified as a non-negative octal integer that is the sum of the following permissions: 0400:: read by owner 0200:: write by owner 0100:: execute/search by owner 0040:: read by group 0020:: write by group 0010:: execute/search by group 0004:: read by others 0002:: write by others 0001:: execute/search by others === Symbolic form In the symbolic form, the mask is specified as a symbolic expression that denotes permissions that are *not* included in the mask. The entire expression is one or more {{clause}}s separated by comma. A {{clause}} is a sequence of {{who}}s followed by one or more {{action}}s. A {{who}} is one of: +u+:: owner +g+:: group +o+:: others +a+:: all of owner, group, and others An empty sequence of {{who}}s is equivalent to who +a+. An {{action}} is an {{operator}} followed by {{permission}}. An {{operator}} is one of: +=+:: set {{who}}'s permission to {{permission}} +++:: add {{permission}} to {{who}}'s permission +-+:: remove {{permission}} from {{who}}'s permission and {{permission}} is one of: +r+:: read +w+:: write +x+:: execute/search +X+:: execute/search (only if some user already has execute/search permission) +s+:: set-user-ID and set-group-ID +u+:: user's current permissions +g+:: group's current permissions +o+:: others' current permissions but more than one of +r+, +w+, +x+, +X+, and +s+ can be specified after a single {{operand}}. For example, the command +umask u=rwx,go+r-w+ - unmasks the user's read, write, and execute/search permissions; - unmasks the group's and others' read permission; and - masks the group's and others' write permission. [[exitstatus]] == Exit status The exit status of the umask built-in is zero unless there is any error. [[notes]] == Notes The umask built-in is a link:builtin.html#types[semi-special built-in]. The POSIX standard does not require the default output format (used when the +-S+ option is not specified) to be the octal integer form. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_unalias.txt000066400000000000000000000015001354143602500155140ustar00rootroot00000000000000= Unalias built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Unalias built-in The dfn:[unalias built-in] undefines link:syntax.html#aliases[aliases]. [[syntax]] == Syntax - +unalias {{name}}...+ - +unalias -a+ [[description]] == Description The unalias built-in removes the definition of the link:syntax.html#aliases[aliases] specified by operands. [[options]] == Options +-a+:: +--all+:: Undefine all aliases. [[operands]] == Operands {{name}}:: The name of an alias to be undefined. [[exitstatus]] == Exit status The exit status of the unalias built-in is zero unless there is any error. It is an error to specify the name of a non-existing alias as {{name}}. [[notes]] == Notes The unalias built-in is a link:builtin.html#types[semi-special built-in]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_unset.txt000066400000000000000000000024561354143602500152310ustar00rootroot00000000000000= Unset built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Unset built-in The dfn:[unset built-in] undefines variables or functions. [[syntax]] == Syntax - +unset [-fv] [{{name}}...]+ [[description]] == Description The unset built-in removes the definition of the link:params.html#variables[variables] or link:exec.html#function[functions] specified by operands. It is not an error if any of the specified variables or functions do not exist; they are silently ignored. [[options]] == Options +-f+:: +--functions+:: Undefine functions. +-v+:: +--variables+:: Undefine variables. These options are mutually exclusive: only the last specified one is effective. If neither is specified, +-v+ is assumed. [[operands]] == Operands {{name}}:: The name of a variable or function to be undefined. [[exitstatus]] == Exit status The exit status of the unset built-in is zero unless there is any error. [[notes]] == Notes The unset built-in is a link:builtin.html#types[special built-in]. Although yash does not do so, the POSIX standard allows removing a function if neither of the +-f+ and +-v+ options is specified and the specified variable does not exist. At least one {{name}} operand must be specified in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/_wait.txt000066400000000000000000000040241354143602500150300ustar00rootroot00000000000000= Wait built-in :encoding: UTF-8 :lang: en //:title: Yash manual - Wait built-in The dfn:[wait built-in] waits for jobs to terminate. [[syntax]] == Syntax - +wait [{{job}}...]+ [[description]] == Description The wait built-in waits for background jobs to terminate. If link:job.html[job] control is enabled, stopped jobs are considered as terminated. The built-in can be used to wait for link:syntax.html#async[asynchronous commands] if job control is disabled. If the shell receives a signal while the built-in is waiting and if a link:_trap.html[trap] has been set for the signal, then the trap is executed and the built-in immediately finishes (without waiting for the jobs). If the shell receives a SIGINT signal when job control is enabled, the built-in aborts waiting. If the shell is link:interact.html[interactive], link:job.html[job-controlling], and not in the link:posix.html[POSIXly-correct mode], the job status is printed when the job is terminated or stopped. [[operands]] == Operands {{job}}:: The link:job.html#jobid[job ID] of the job or the process ID of a process in the job. If no {{job}}s are specified, the built-in waits for all existing jobs. If the specified job does not exist, the job is considered to have terminated with the exit status of 127. [[exitstatus]] == Exit status If no {{job}}s were specified and the built-in successfully waited for all the jobs, the exit status is zero. If one or more {{job}}s were specified, the exit status is that of the last {{job}}. If the built-in was aborted by a signal, the exit status is an integer (> 128) that denotes the signal. If there was any other error, the exit status is between 1 and 126 (inclusive). [[notes]] == Notes The wait built-in is a link:builtin.html#types[semi-special built-in]. The process ID of the last process of a job can be obtained by the link:params.html#sp-exclamation[+!+ special parameter]. You can use the link:_jobs.html[jobs built-in] as well to obtain process IDs of job processes. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/asciidoc.conf000066400000000000000000000016061354143602500156140ustar00rootroot00000000000000[macros] (?su)dfn:\[(?P.*?)\]=dfn (?su)dfn:(?P\w+)\[(?P.*?)\]=dfnid (?su)lang:(?P\w+)\[(?P.*?)\]=lang [quotes] ~= ((|))= (((|)))= +=code ++=#code `=#code {{|}}=#var ifdef::basebackend-html[] [tags] code=| var=| [dfn-inlinemacro] {value} [dfnid-inlinemacro] {value} ifdef::backend-xhtml11[] [lang-inlinemacro] {value} endif::backend-xhtml11[] ifndef::backend-xhtml11[] [lang-inlinemacro] {value} endif::backend-xhtml11[] endif::basebackend-html[] ifdef::basebackend-docbook[] [tags] code=| var=| [dfn-inlinemacro] {value} [dfnid-inlinemacro] {value} [lang-inlinemacro] {value} endif::basebackend-docbook[] # vim: set filetype=cfg: yash-2.49/doc/asciidoc.css000066400000000000000000000033451354143602500154610ustar00rootroot00000000000000html, body { color: black; background: white; } body { margin: 1em 5%; text-align: justify; } h1, h2, h3, h4, h5, h6 { padding: 0; padding-left: 0.5em; border-style: none none solid solid; border-bottom-width: 1px; } h1, h2, h3 { margin: 1em -3.5% 0.5em; } h4, h5, h6 { margin: 1em -2.5% 0.5em; } h1 { border-color: orange; border-left-width: 1em; } h2 { border-color: navy; border-left-width: 0.7em; } h3 { border-color: green; border-left-width: 0.5em; } .sectionbody { /*margin: 0 3%;*/ } #footer { margin: 1em -3% 0; font-size: small; border: none; border-top: 2px solid silver; } .title { border-bottom: 1px solid maroon; font-weight: bold; } .list-group p { margin-top: 0.2em; margin-bottom: 0.2em; } .admonitionblock td.icon { vertical-align: top; padding-right: 0.5em; } .exampleblock { margin: 0.8em auto; border: 0.15em dotted maroon; padding: 0 0.5em; } .exampleblock > .title { margin-top: 0.5em; } ul, ol { list-style-position: outside; } ol.arabic { list-style-type: decimal; } ol.loweralpha { list-style-type: lower-alpha; } ol.upperalpha { list-style-type: upper-alpha; } ol.lowerroman { list-style-type: lower-roman; } ol.upperroman { list-style-type: upper-roman; } li p { margin-top: 0.65em; margin-bottom: 0.65em; } dd p { margin-top: 0.1em; margin-bottom: 0.65em; } pre { border: dashed 1px gray; padding: 0.3em; } code, kbd, samp { border: dotted 1px gray; padding: 1px; text-decoration: none; white-space: pre-wrap; } pre > code:only-child, pre > kbd:only-child, pre > samp:only-child { display: block; } pre code, pre kbd, pre samp { white-space: inherit; } code { background: #efe; } kbd { background: #ffd; } samp { background: #eef; } var { color: maroon; background: inherit; } yash-2.49/doc/builtin.txt000066400000000000000000000132641354143602500154010ustar00rootroot00000000000000= Built-in commands :encoding: UTF-8 :lang: en //:title: Yash manual - Built-in commands :description: This page describes common properties of built-ins of yash. dfn:[Built-in commands] are commands that are implemented in the shell and are executed by the shell without external programs. ifdef::basebackend-html[] See the link:index.html#builtins[table of contents] for the list of the built-ins. endif::basebackend-html[] [[types]] == Types of built-in commands There are three types of built-in commands in yash: special built-in commands, semi-special built-in commands and regular built-in commands. dfn:[Special built-in commands] are much more important commands than others. They are executed regardless of whether the corresponding external commands exist or not. Results of variable assignments that occur in a link:syntax.html#simple[simple command] that invokes a special built-in last after the command has finished. Moreover, in the link:posix.html[POSIXly-correct mode], a link:interact.html[non-interactive] shell immediately exits with a non-zero exit status when a redirect error, assignment error, or misuse of option or operand occurs in a special built-in command. dfn:[Semi special built-in commands] are the second important built-in commands. They are executed regardless of whether the corresponding external commands exist or not. In other parts they are the same as regular built-in commands. dfn:[Regular built-in commands] are less important built-in commands including commands that can be implemented as external commands or are not listed in POSIX. In the POSIXly-correct mode, a regular built-in is executed only when a corresponding external command is link:exec.html#search[found in PATH]. [[argsyntax]] == Syntax of command arguments In this section we explain common rules about command arguments. The built-in commands of yash follow the rules unless otherwise stated. There are two types of command arguments. One is options and the other is operands. An option is an argument that starts with a hyphen (+-+) and changes the way the command behaves. Some options take arguments. An operand is an argument that is not an option and specifies objects the command operates on. If you specify more than one option to a command, the order of the options are normally not significant. The order of operands, however, affects the command behavior. An option is either a single-character option or a long option. A single-character option is identified by one alphabetic character. A long option is identified by multiple alphabetic characters. The POSIX standard only prescribes single-character options, so in the link:posix.html[POSIXly-correct mode] you cannot use long options. A single-character option is composed of a hyphen followed by a letter. For example, +-a+ is a single-character option. A single-character option that takes an argument requires the argument to be just after the option name. .The set built-in and single-character options ==== For the set built-in, +-m+ is a single-character option that does not take an argument and +-o+ is one that takes an argument. - +set -o errexit -m+ - +set -oerrexit -m+ In these two command lines, +errexit+ is the argument to the +-o+ option. ==== In the second example above, the +-o+ option and its argument are combined into a single command line argument. The POSIX standard deprecates that style and any POSIX-conforming applications must specify options and their arguments as separate command line arguments, although yash accepts both styles. You can combine single-character options that do not take arguments into a single command line argument. For example, the three options +-a+, +-b+ and +-c+ can be combined into +-abc+. A long option is composed of two hyphens followed by an option name. For example, +--long-option+ is a long option. You can omit some last characters of a long option name as long as it is not ambiguous. For example, you can use +--long+ instead of +--long-option+ if there is no other options beginning with +--long+. Like a single-character option, a long option that takes an argument requires the argument to be a command line argument just after the option name or to be specified in the same command line argument as the option name, separated by an equal sign (+=+). .The fc built-in and long options ==== For the fc built-in, +--quiet+ is a long option that does not take an argument and +--editor+ is one that takes an argument. - +fc --editor vi --quiet+ - +fc --editor=vi --quiet+ In these command lines, +vi+ is the argument to the +--editor+ option. ==== Arguments that are not options (nor arguments to them) are interpreted as operands. The POSIX standard requires all options should be specified before any operands. Therefore, in the link:posix.html[POSIXly-correct mode], any arguments that come after the first operand are interpreted as operands (even if they look like options). If not in the POSIXly-correct mode, you can specify options after operand. Regardless of whether the shell is in the POSIXly-correct mode or not, an argument that is just composed of two hyphens (+--+) can be used as a separator between options and operands. All command line arguments after the +--+ separator are interpreted as operands, so you can specify operands that start with a hyphen correctly using the separator. .Options and operands to the set built-in ==== - `set -a -b -- -c -d` In this example, +-a+ and +-b+ are options and +-c+ and +-d+ are operands. The +--+ separator itself is neither an option nor an operand. ==== Regardless of whether the shell is in the POSIXly-correct mode or not, an argument that is just composed of a single hyphen (+-+) is interpreted as an operand. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/exec.txt000066400000000000000000000267321354143602500146630ustar00rootroot00000000000000= Command execution :encoding: UTF-8 :lang: en //:title: Yash manual - Command execution :description: This page describes how commands are executed by yash. This section describes how commands are executed. [[simple]] == Execution of simple commands link:syntax.html#simple[A simple command] is executed as follows: . All tokens in the simple command are link:expand.html[expanded] except for assignment and redirection tokens. If an error occurs during expansion, the execution of the simple command is aborted with a non-zero exit status. + In the following steps, the first word of the expansion results is referred to as dfn:[command name], and the other words as dfn:[command arguments]. If there is only one word of the expansion results, there are no command argument words. If there are none of the expansion results, there is no command name either. . link:redir.html[Redirection] specified in the command, if any, is processed. The word token after each redirection operator is expanded. If an error occurs during processing redirection (including when expanding the word token), the execution of this simple command is aborted with a non-zero exit status. . Assignments specified in the command, if any, are processed. For each assignment token, the value is expanded and assigned to the specified link:params.html#variables[variable]. If an error occurs during assignments (including when expanding the values to be assigned), the execution of this simple command is aborted with a non-zero exit status. + -- - If there is no command name or the name denotes a link:builtin.html#types[special built-in], the assignments are permanent: the assigned values remain after the command has finished (until the variable is reassigned). - Otherwise, the assignments are temporary: the assigned values only last during the execution of this simple command. -- + The assigned variables are automatically link:params.html#variables[exported] when the command name is specified or the link:_set.html#so-allexport[all-export option] is enabled. + [NOTE] In other shells, assignments may behave differently: For special built-ins and functions, assigned variables may not be exported. For functions, assigned variables may be persistent, that is, may remain even after the execution of the simple command. . If there is no command name, the command execution ends with the exit status of zero (unless there are any link:expand.html#cmdsub[command substitutions] in the command, in which case the exit status of the simple command is that of the last executed command substitution). . A command to be executed is determined using the <> and the command is executed. + -- - If the command is an external command, the command is executed by creating a new <> and calling the ``exec'' system call in the subshell. The command name and arguments are passed to the executed command. Exported variables are passed to the executed command as environment variables. - If the command is a link:builtin.html[built-in], the built-in is executed with the command arguments passed to the built-in. - If the command is a <>, the contents of the function are executed with the command arguments as function arguments. If the command was executed, the exit status of this simple command is that of the executed command. If the algorithm failed to determine a command, no command is executed and the exit status is 127. If the shell failed to execute the determined command, the exit status is 126. If the executed command was killed by a signal, the exit status is the signal number plus 384. [NOTE] In shells other than yash, the exit status may be different when the command was killed by a signal, because the POSIX standard only requires that the exit status be "greater than 128." If the shell is not in the link:posix.html[POSIXly-correct mode] and the algorithm failed to determine a command, the command ifdef::basebackend-html[] pass:[eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"`] is evaluated. During the command execution, link:params.html#positional[positional parameters] are temporarily set to the command name and arguments that resulted in the first step. Any <> defined during the execution are removed when the execution is finished. The +HANDLED+ local variable is automatically defined with the initial value being the empty string. If the +HANDLED+ variable has a non-empty value when the execution of the command string is finished, the shell pretends that the command was successfully determined and executed. The exit status of the simple command is that of the command string in this case. -- [[search]] === Command search A command that is executed in a simple command is determined by the command name using the following algorithm: . If the command name contains a slash (+/+), the whole name is treated as the pathname of an external command. The external command is determined as the executed command. . If the command name is a link:builtin.html#types[special built-in], the built-in is determined as the executed command. . If the command name is the name of an existing <>, the function is determined as the executed command. . If the command name is a link:builtin.html#types[semi-special built-in], the built-in is determined as the executed command. . If the command name is a link:builtin.html#types[regular built-in], the built-in is determined as the executed command unless the shell is in the link:posix.html[POSIXly-correct mode]. . The shell searches the PATH for a executed command: + -- The value of the link:params.html#sv-path[+PATH+ variable] is separated by colons. Each separated part is considered as a directory pathname (an empty pathname denotes the current working directory). The shell searches the directories (in the order of appearance) and checks if any directory directly contains an executable regular file whose name is equal to the command name. If such a file is found: - If the command name is the name of a built-in, the built-in is determined as the executed command. - Otherwise, the file is determined as the executed command. (The file will be executed as an external command.) If no such file is found, no command is determined as the executed command. -- When the shell finds a file that matches the command name during the search above, the shell remembers the pathname of the file if it is an absolute path. When the algorithm above is used for the same command name again, the shell skips searching and directly determines the command to be executed. If an executable regular file no longer exists at the remembered pathname, however, the shell searches again to update the remembered pathname. You can manage remembered pathnames using the link:_hash.html[hash built-in]. [[exit]] == Termination of the shell The shell exits when it reached the end of input and has parsed and executed all input commands or when the link:_exit.html[exit built-in] is executed. The exit status of the shell is that of the last command the shell executed (or zero if no commands were executed). The exit status of the shell is always between 0 and 255 (inclusive). If the exit status of the last command is 256 or larger, the exit status of the shell will be the remainder of the exit status divided by 256. If an exit handler has been registered by the link:_trap.html[trap built-in], the handler is executed just before the shell exits. The exit status of the commands executed in the handler does not affect the exit status of the shell. If a non-link:interact.html[interactive] shell encountered one of the following errors, the shell immediately exits with a non-zero exit status: - A command cannot be parsed due to an syntax error (except during link:invoke.html#init[shell initialization]). - An error occurs during execution of a link:builtin.html#types[special built-in] in the link:posix.html[POSIXly-correct mode]. - A link:redir.html[redirection] error occurs in a link:syntax.html#simple[simple command] whose command name is a special built-in and the shell is in the POSIXly-correct mode. - An assignment error occurs in a simple command. - An error occurs during link:expand.html[expansion] (except during shell initialization). [NOTE] Some shells other than yash exit when they fail to find a command to execute in <>. [[function]] == Functions dfn:[Functions] allow executing a link:syntax.html#compound[compound command] as a link:syntax.html#simple[simple command]. A function can be defined by the link:syntax.html#funcdef[function definition command] and executed by a simple command. You can use the link:_unset.html[unset built-in] to remove function definitions. There are no functions predefined when yash is started. A function is executed by executing its body, which is a compound command. While the function is being executed, link:params.html#positional[positional parameters] are set to the arguments given to the function. The old positional parameters are restored when the function execution finishes. [[localvar]] === Local variables dfn:[Local variables] are temporary variables that are defined in a function and exist during the function execution only. They can be defined by the link:_typeset.html[typeset built-in] or implicitly created by a link:syntax.html#for[for loop]. They are removed when the function execution finishes. Local variables may _hide_ variables that have already been defined before the function execution had started. An existing variable becomes inaccessible if a local variable of the same name is defined in a function. The old variable becomes accessible again when the function execution finishes. You cannot create a local variable when not executing a function. A normal variable is created if you try to do so. [[environment]] == Command execution environment The shell holds following properties during execution. - The working directory - Open file descriptors - The file creation mask (link:_umask.html[umask]) - The set of signals whose handler is set to ``ignore'' (link:_trap.html[trap]) - link:params.html#variables[Environment variables] - Resource limits (link:_ulimit.html[ulimit]) Those properties are inherited from the invoker of the shell to the shell, and from the shell to each external command executed by the shell. The properties can be changed during the execution of the shell by built-in commands, variable assignments, etc. [[subshell]] === Subshells A dfn:[subshell] is a copy of the shell process. Subshells are used in execution of link:syntax.html#grouping[groupings], link:syntax.html#pipelines[pipelines], etc. Subshells inherit functions, aliases, etc. defined in the shell as well as the properties above since subshells are copies of the shell process. Notable exceptions are: - Traps registered by the link:_trap.html[trap built-in] are all reset in subshells except for ones whose action is set to ``ignore''. (See below) - The link:interact.html[interactive] mode and link:job.html[job control] are disabled in subshells. Jobs are not inherited by subshells. Subshells are executed independently of the original shell, so changes of any properties above do not affect those of the original shell. [NOTE] If the subshell contains a single trap built-in, some shells (but not yash) may not reset the traps on entry to the subshell. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/expand.txt000066400000000000000000000554401354143602500152140ustar00rootroot00000000000000= Word expansions :encoding: UTF-8 :lang: en //:title: Yash manual - Word expansions :description: This page describes word expansions supported by yash. dfn:[Word expansion] is substitution of part of a word with another particular string. There are seven types of word expansions: . <> . <> . <> . <> . <> . <> . <> (globbing) These types of expansions are performed in the order specified above. Tilde expansion, parameter expansion, command substitution, and arithmetic expansion are called the dfn:[four expansions]. [[tilde]] == Tilde expansion In dfn:[tilde expansion], parts of words that start with a tilde (+~+) are substituted with particular pathnames. The part of each word that gets substituted is from the beginning of the word, which is a tilde, up to (but not including) the first slash (+/+) in the word. If the word does not contain a slash, the whole word is substituted. If any character in the substituted part is link:syntax.html#quotes[quoted], tilde expansion is not performed on the word. The results of expansion are determined by the format of the substituted part: +~+:: A single tilde is substituted with the value of the link:params.html#sv-home[+HOME+ variable]. +~{{username}}+:: A tilde followed by a user name is substituted with the pathname of the user's home directory. +~++:: +~++ is substituted with the value of the link:params.html#sv-pwd[+PWD+ variable]. +~-+:: +~-+ is substituted with the value of the link:params.html#sv-oldpwd[+OLDPWD+ variable]. +~+{{n}}+:: +~-{{n}}+:: where {{n}} is a non-negative integer. This type of tilde expansion yields the pathname of a directory of which +~+{{n}}+ or +~-{{n}}+ is the index in the directory stack. When tilde expansion is performed on the value of a variable assignment that occurs during execution of a link:syntax.html#simple[simple command], the value is considered as a colon-separated list of words and those words are each subject to tilde expansion. For example, the variable assignment ---- VAR=~/a:~/b:~/c ---- is equivalent to ---- VAR=/home/foo/a:/home/foo/b:/home/foo/c ---- if the value of +HOME+ variable is +/home/foo+. The POSIX standard does not prescribe how the shell should behave when it encounters an error during tilde expansion (e.g., when the +HOME+ variable is not defined). Yash silently ignores any errors during tilde expansion; the part of the word that would be substituted is left intact. In the link:posix.html[POSIXly-correct mode], tilde expansion supports the formats of +~+ and +~{{username}}+ only. [[params]] == Parameter expansion dfn:[Parameter expansion] expands to the value of a parameter. The syntax of typical, simple parameter expansion is +${{{parameter}}}+, which expands to the value of the parameter whose name is {{parameter}}. You can omit the braces (e.g., +${{parameter}}+) if - {{parameter}} is a link:params.html#special[special parameter], - {{parameter}} is a link:params.html#positional[positional parameter] whose index is a one-digit integer, or - {{parameter}} is a variable and the parameter expansion is not followed by a character that can be used as part of a variable name. + ==== For example, +${path}-name+ is equivalent to +$path-name+, but +${path}name+ and +$pathname+ are different. ==== If {{parameter}} is none of a special parameter, positional parameter, and variable, it is a syntax error. (Some shells other than yash may treat such a case as an expansion error.) If the link:_set.html#so-unset[unset option] is disabled and the {{parameter}} is an undefined variable, it is an expansion error. If the unset option is enabled, an undefined variable expands to the empty string. More complex syntax of parameter expansion allows modifying the value of a parameter. Parameter expansion:: +${ {{prefix}} {{parameter}} {{index}} {{modifier}} }+ The spaces in the syntax definition above are for readability only and must be omitted. You can omit {{prefix}}, {{index}}, and/or {{modifier}}. [[param-prefix]] === Prefix The {{prefix}}, if any, must be a hash sign (+#+). If a parameter expansion has the prefix, the result of expansion is the number of characters in the value this expansion would be expanded to without the prefix. [[param-name]] === Parameter name The parameter name ({{parameter}}) must be either - a name of a special parameter, positional parameter, or variable; or - another parameter expansion, <>, or <>. The parameter expansion is expanded to the value of the {{parameter}}. If {{parameter}} is an link:params.html#arrays[array] variable, the values of the array are <> like the link:params.html#sp-at[+@+ special parameter] unless the index +[*]+ is specified. If {{parameter}} is another expansion, it is called a dfn:[nested expansion]. Nested expansion cannot be used in the link:posix.html[POSIXly-correct mode]. The braces (+{ }+) of a nested parameter expansion cannot be omitted. [[param-index]] === Index An {{index}} allows extracting part of the parameter value (or some of array values). Index:: +[{{word1}}]+ + +[{{word1}},{{word2}}]+ where {{word1}} and {{word2}} are parsed in the same manner as normal tokens except that they are always delimited by +,+ or +]+ and can contain whitespace characters. If there is an {{index}} in a parameter expansion, it is interpreted as follows: . Words {{word1}} and {{word2}} are subjected to parameter expansion, <>, and <>. . If there is no {{word2}} and if {{word1}} expands to one of +*+, +@+, and +#+, then that is the interpretation of {{index}} and the next step is not taken. . The results of the previous steps (the expanded {{word1}} and {{word2}}) are interpreted and evaluated as an arithmetic expression in the same manner as in arithmetic expansion. The resulting integers are the interpretation of {{index}}. If the results are not integers, it is an expansion error. If there is no {{word2}}, it is assumed that {{word2}} is equal to {{word1}}. If {{parameter}} is an link:params.html#arrays[array] variable, the {{index}} specifies the part of the array. If {{parameter}} is either the link:params.html#sp-asterisk[+*+] or link:params.html#sp-at[+@+] special parameter, the {{index}} specifies the index range of positional parameters. In other cases, the {{index}} specifies the index range of a substring of the parameter value that is being expanded. In all cases, the specified range of the array values, positional parameters, or parameter value remains in the results of the expansion and other values are dropped. If the interpretation of {{index}} is one or two integers, the following rules apply: - If the interpreted index value is negative, it _wraps around_. For example, the index value of -1 corresponds to the last value/character. - It is not an error when the index value is out of range. Existing values/characters within the range are just selected. - If the interpretation of either {{word1}} or {{word2}} is 0, the range is assumed empty and the expansion results in nothing. If the interpretation of {{index}} is one of +*+, +@+, and +#+, it is treated as follows: +*+:: If {{parameter}} is an array, all the array values are link:expand.html#split[field-split] or concatenated in the same manner as the link:params.html#sp-asterisk[+*+ special parameter]. If {{parameter}} is the +*+ or +@+ special parameter, the positional parameters are likewise field-split or concatenated. In other cases, the interpretation of {{index}} is treated as if the interpretation is the two integers 1 and -1. +@+:: The interpretation of {{index}} is treated as if the interpretation is the two integers 1 and -1. +#+:: The interpretation of the +#+ {{index}} is special in that it does not simply specify a range. Instead, the expanded values are substituted with the count. + If {{parameter}} is an array, the result of this parameter expansion will be the number of values in the array being expanded. If {{parameter}} is the +*+ or +@+ special parameter, the result will be the number of current positional parameters. Otherwise, the result will be the number of characters in the value that is being expanded. If a parameter expansion does not contain an {{index}}, it is assumed to be +[@]+. In the link:posix.html[POSIXly-correct mode], {{index}} cannot be specified. .Expansion of a normal variable ==== The following commands will print the string +ABC+: ---- var='123ABC789' echo "${var[4,6]}" ---- ==== .Expansion of positional parameters ==== The following commands will print the string +2 3 4+: ---- set 1 2 3 4 5 echo "${*[2,-2]}" ---- ==== .Expansion of an array ==== The following commands will print the string +2 3 4+: ---- array=(1 2 3 4 5) echo "${array[2,-2]}" ---- ==== [[param-mod]] === Modifier You can modify the value to be expanded by using dfn:[modifiers]: +-{{word}}+:: If the parameter name ({{parameter}}) is an undefined variable, the parameter expansion is expanded to {{word}}. It is not treated as an error if the link:_set.html#so-unset[unset option] is disabled. ++{{word}}+:: If the parameter name ({{parameter}}) is an existing variable, the parameter expansion is expanded to {{word}}. It is not treated as an error if the link:_set.html#so-unset[unset option] is disabled. +={{word}}+:: If the parameter name ({{parameter}}) is an undefined variable, {{word}} is assigned to the variable and the parameter expansion is expanded to {{word}}. It is not treated as an error if the link:_set.html#so-unset[unset option] is disabled. +?{{word}}+:: If the parameter name ({{parameter}}) is an undefined variable, {{word}} is printed as an error message to the standard error. If {{word}} is empty, the default error message is printed instead. +:-{{word}}+:: +:+{{word}}+:: +:={{word}}+:: +:?{{word}}+:: These are similar to the four types of modifiers above. The only difference is that, if {{parameter}} exists and has an empty value, it is also treated as an undefined variable. +#{{word}}+:: The shell performs link:pattern.html[pattern matching] against the value that is being expanded, using {{word}} as a pattern. If {{word}} matches the beginning of the value, the matching part is removed from the value and the other part remains as expansion results. The shortest matching is used if more than one matching is possible. +##{{word}}+:: This is similar to +#{{word}}+ above. The only difference is that the longest matching is used if more than one matching is possible. +%{{word}}+:: This is similar to +#{{word}}+ above. The only difference is that matching is tried at the end of the value rather than at the beginning: if {{word}} matches the end of the value, the matching part is removed from the value and the other part remains as expansion results. +%%{{word}}+:: This is similar to +%{{word}}+ above. The only difference is that the longest matching is used if more than one matching is possible. +/{{word1}}/{{word2}}+:: The shell performs link:pattern.html[pattern matching] against the value that is being expanded, using {{word1}} as a pattern. If {{word1}} matches any part of the value, the matching part is replaced with {{word2}} and the whole value after the replacement remains as expansion results. If {{word1}} matches more than one part of the value, only the first part is replaced. The shortest matching is replaced if more than one matching is possible for the same starting point in the value. + This modifier cannot be used in the link:posix.html[POSIXly-correct mode]. +/#{{word1}}/{{word2}}+:: This is similar to +/{{word1}}/{{word2}}+ above. The only difference is that {{word1}} matches only at the beginning of the value being expanded. +/%{{word1}}/{{word2}}+:: This is similar to +/{{word1}}/{{word2}}+ above. The only difference is that {{word1}} matches only at the end of the value being expanded. +//{{word1}}/{{word2}}+:: This is similar to +/{{word1}}/{{word2}}+ above. The only difference is that all matched parts are replaced if {{word1}} matches more than one part of the value. +:/{{word1}}/{{word2}}+:: This is similar to +/{{word1}}/{{word2}}+ above. The only difference is that the value is replaced only when {{word1}} matches the whole value. In all types of modifiers above, words are subjected to the four expansions when (and only when) they are used. If {{parameter}} is an array variable or the link:params.html#sp-at[+@+] or link:params.html#sp-asterisk[+*+] special parameter, modifiers affect each value of the array or all positional parameters. [[cmdsub]] == Command substitution dfn:[Command substitution] expands to output of commands specified. Command substitution:: +$({{commands}})+ + +`{{commands}}`+ When command substitution is evaluated, {{commands}} are executed by a link:exec.html#subshell[subshell] with output pipelined to the shell. When the {{commands}} finished, command substitution is substituted with the output of the {{commands}}. Any trailing newline characters in the output are ignored. When command substitution of the form +$({{commands}})+ is parsed, the {{commands}} are parsed carefully so that complex commands such as nested command substitution are parsed correctly. If {{commands}} start with +(+, you should put a space before {{commands}} so that the whole command substitution is not confused with <>. If the shell is in the link:posix.html[POSIXly-correctly mode], the {{commands}} are parsed each time the command substitution is expanded; otherwise, {{commands}} are parsed only when the command substitution is parsed. If command substitution is of the form +`{{commands}}`+, the {{commands}} are not parsed when the command substitution is parsed; the {{commands}} are parsed each time the command substitution is expanded. The end of {{commands}} is detected by the first backquote character (+`+) after the beginning of {{commands}} that is not link:syntax.html#quotes[quoted] by a backslash. Backquotes that are part of {{commands}} (typically used for nested command substitution) must be quoted by backslashes. In {{commands}}, backslashes are treated as quotes only when preceding a dollar (+$+), backquote, newline, or another backslash. Additionally, if the command substitution occurs inside double quotes, double quotes in {{commands}} must be quoted with a backslash. Those backslashes are removed before {{commands}} are parsed. [[arith]] == Arithmetic expansion dfn:[Arithmetic expansion] evaluates an arithmetic expression and expands to the value of the expression. Arithmetic expansion:: +$(({{expression}}))+ When arithmetic expansion is expanded, the {{expression}} is subject to <>, <>, and (nested) arithmetic expansion. The {{expression}} is parsed in (almost) same manner as an expression of the C programming language. Yash allows an expression to be either an integer (of the long type in C) or a floating-point number (of the double type in C). An operation on integers yields an integer and an operation involving a floating-point number yields a floating-point number. In the link:posix.html[POSIXly-correct mode], you can use integers only. The following operators are available (in the order of precedence): . +( )+ . `++` +--+ (postfix operators) . `++` +--+ `+` +-+ +~+ +!+ (prefix operators) . +*+ +/+ +%+ . `+` +-+ (binary operators) . +<<+ +>>+ . +<+ +<=+ +>+ +>=+ . +==+ +!=+ . +&+ . +^+ . +|+ . +&&+ . +||+ . +? :+ . +=+ +*=+ +/=+ +%=+ `+=` +-=+ +<<=+ +>>=+ +&=+ +^=+ +|=+ The `++` and `--` operators cannot be used in the POSIXly-correct mode. An atomic expression can be one of an integer literal, a floating-point number literal, and a variable. Literals are parsed in the same manner as in C. An octal integer literal starts with +0+, and hexadecimal with +0x+. A floating-point number literal may have an exponent (i.e. `1.23e+6`). A variable with a non-numeric value will result in an error when parsed as a number. An unset variable is treated as a value of zero if the link:_set.html#so-unset[unset option] is enabled. In the POSIXly-correct mode, variables are always parsed as numbers. Otherwise, variables are parsed only when they are used as numbers in computation. Unparsed variables are left intact. ---- set +o posixly-correct foo=bar echo $((0 ? foo : foo)) # prints "bar" echo $((foo + 0)) # error ---- [[brace]] == Brace expansion dfn:[Brace expansion] expands to several split words with preceding and succeeding portions duplicated to each split words. Brace expansion is expanded only when the link:_set.html#so-braceexpand[brace-expand option] is enabled. Comma-separated brace expansion:: +{{{word1}},{{word2}},...,{{wordn}}}+ Range brace expansion:: +{{{start}}..{{end}}}+ + +{{{start}}..{{end}}..{{delta}}}+ Comma-separated brace expansion is expanded to each comma-separated word. For example, +a{1,2,3}b+ is expanded to the three words +a1b+, +a2b+, and +a3b+. Range brace expansion is expanded to integers in the range defined by {{start}} and {{end}}. The difference between each integer can be defined by {{delta}}. If {{start}} is larger than {{end}}, the results will be in descending order. When +..{{delta}}+ is omitted, it defaults to 1 or -1. For example, +a{1..3}b+ is expanded to the three words +a1b+, +a2b+, and +a3b+; and +a{1..7..2}b+ to the four words +a1b+, +a3b+, +a5b+, and +a7b+. Multiple brace expansions can be used in one word. Brace expansions can also be nested. You can link:syntax.html#quotes[quote] braces and/or commas to prevent them from being treated as brace expansion. Any errors in brace expansion are silently ignored. [[split]] == Field splitting In dfn:[field splitting], words are split at predefined separators. Field splitting can occur only within parts of words that resulted from <>, <>, and <> that are not between link:syntax.html#quotes[double-quotation marks]. Expansion results of the link:params.html#sp-at[+@+ special parameter] are exceptionally split even between double-quotation marks. Separators used in field splitting are defined by the value of the link:params.html#sv-ifs[+IFS+ variable]. If the variable does not exist, the value is assumed to be the three characters of space, tab, and newline. Characters included in the value of the +IFS+ variable are called dfn:[IFS characters]. IFS characters that are any of space, tab, and newline are called dfn:[IFS whitespace] and other IFS characters are called dfn:[IFS non-whitespace]. Field splitting is performed as follows: . The shell searches words for split points. A split point is one or more adjacent IFS characters within the word portions where field splitting can occur. The following steps are taken for each split point found. . If the split point includes one or more IFS non-whitespaces, all the IFS whitespaces in the split point are ignored and the word is split at each IFS non-whitespace in the split point. . If the split point includes no IFS non-whitespaces, the word is split at the split point unless it is at the beginning or end of the word. . The split points are removed from the results. Finally, the last word is removed from the results if: - the link:_set.html#so-emptylastfield[empty-last-field option] is not enabled; - the result is more than one word; and - the last word is empty. [NOTE] Words are not split at all when the value of the +IFS+ variable is empty. [[glob]] == Pathname expansion dfn:[Pathname expansion] performs pattern matching and expands to pathnames matched by the pattern. A word subjected to pathname expansion is treated as a link:pattern.html[pattern]. If one or more pathnames are found that are matched by the pattern, the pathnames become the results of the pathname expansion. Pathname expansion is not performed when the link:_set.html#so-glob[glob option] is disabled. The shell searches readable directories for matching pathnames. Unreadable directories are silently ignored. The following options affect the behavior of pathname expansion: [[opt-nullglob]]null-glob:: This option affects the result of pathname expansion when no matching pathnames are found. If enabled, the result is no word. If disabled, the result is the original pattern word. [[opt-caseglob]]case-glob:: This option specifies case-sensitivity in matching. If enabled, pattern matching is done case-sensitively. [[opt-dotglob]]dot-glob:: This option affects matching of filenames that start with a period (+.+). If disabled, a period at the beginning of a filename does not match wildcard patterns (+?+ and +*+) or bracket expressions. If enabled, there is no such special treatment of periods. [[opt-markdirs]]mark-dirs:: If enabled, each resulting pathname that is a directory name is suffixed by a slash (+/+). [[opt-extendedglob]]extended-glob:: ifdef::basebackend-html[] This option enables the <>. endif::basebackend-html[] ifdef::basebackend-docbook[] This option enables the extension. (See below) endif::basebackend-docbook[] Any errors in pathname expansion are silently ignored. If the word is an invalid pattern, it just becomes the result. The results depend on the null-glob option when no matching pathnames are found. Pattern matching is done for each filename (or pathname component) of pathnames. The shell skips matching for literal patterns that contain no wildcards or bracket expressions. As a result, the patterns +/*/foo+ and +/*/fo[o]+ may yield different expansion results when the <> option is disabled; for example, the pattern +/*/fo[o]+ matches the pathname +/bar/FOO+ but the pattern +/*/foo+ does not because matching is skipped for +foo+. [[extendedglob]] === Extension in pathname expansion The following patterns can be used when the <> option is enabled. +**+:: The directory is searched recursively and the pattern matches any number of directory filenames (each separated by a slash). Any directory whose name begins with a period is excluded from search. For example, the pattern +dir/**/file+ can match the pathnames +dir/file+, +dir/foo/file+, +dir/a/b/c/file+, etc. + This pattern is not effective when appearing at the end of the whole pattern (i.e. +foo/bar/**+). +.**+:: This pattern is like +**+, but all directories are searched including ones with a name starting with a period. +***+:: This pattern is like +**+, but if a symbolic link to a directory is found during recursive search, the directory is searched recursively as well. +.***+:: This pattern is like +***+, but all directories are searched including ones with a name starting with a period. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/faq.txt000066400000000000000000000046741354143602500145070ustar00rootroot00000000000000= Frequently asked questions and troubleshooting :encoding: UTF-8 :lang: en //:title: Yash manual - FAQ and troubleshooting :description: Frequently asked questions and troubleshooting for yash. [[unicode]] == How can I use Unicode (non-ASCII) characters? You need to set locale environment variables to enable Unicode character support. If the variables have already been configured for your preference, you probably don't have to do anything. To check the current locale configurations, you can use the +locale+ command: ---- $ locale LANG= LC_CTYPE="en_US.utf8" LC_NUMERIC="en_US.utf8" LC_TIME="en_US.utf8" LC_COLLATE="en_US.utf8" LC_MONETARY="en_US.utf8" LC_MESSAGES="en_US.utf8" LC_PAPER="en_US.utf8" LC_NAME="en_US.utf8" LC_ADDRESS="en_US.utf8" LC_TELEPHONE="en_US.utf8" LC_MEASUREMENT="en_US.utf8" LC_IDENTIFICATION="en_US.utf8" LC_ALL=en_US.utf8 ---- In this example, the +locale+ command shows that all the locale setting categories are configured for the English language, the United States region, and the UTF-8 encoding. If the current configuration does not seem to match your preference, set the +LC_ALL+ variable like this: ---- export LC_ALL=en_US.utf8 ---- If you want to use other languages, regions, or encodings, you have to set the variable to a different value. Please consult your OS's documentation to learn how to configure these variables in detail. If you want to apply the same configuration every time you start yash, write the command in ~/.yashrc or ~/.yash_profile. If yash still rejects Unicode characters being entered, see the section below regarding line-editing. [[lineediting]] == Line-editing does not work First, type +echo $TERM+ and see if it prints a _sane_ value. +xterm+ is the safest value that should work on any existing environment. Colored versions like +xterm-16color+ and other terminal types like +rxvt+ and +vt100+ may also work. All possible values can be listed by the +toe+ command. It's most desirable to choose a value that matches the actual terminal type you are using, but it might not work if the terminal type you chose is not supported on the system on which yash is running. If so, try changing the +TERM+ value by +export TERM=xterm+, for example, to find a value that works. If line-editing works but you have trouble entering Unicode (non-ASCII) characters, try enabling the link:lineedit.html#options[le-no-conv-meta] option by +set -o le-no-conv-meta+. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/fgrammar.txt000066400000000000000000000252211354143602500155230ustar00rootroot00000000000000= Formal definition of command syntax :encoding: UTF-8 :lang: en //:title: Yash manual - Formal definition of command syntax :description: This page gives the formal definition of yash command syntax. This chapter defines the syntax of the shell command language. [NOTE] Some of the syntactic features described below are not supported in the link:posix.html[POSIXly-correct mode]. [[token]] == Tokenization The characters of the input source code are first delimited into tokens. Tokens are delimited so that the earlier token spans as long as possible. A sequence of one or more unquoted blank characters delimits a token. The following tokens are the operator tokens: `&` `&&` `(` `)` `;` `;;` `|` `||` `<` `<<` `<&` `<(` `<<-` `<<<` `<>` `>` `>>` `>&` `>(` `>>|` `>|` (newline) [NOTE] Unlike other programming languages, the newline operator is a token rather than a white space. Characters that are not blank nor part of an operator compose a word token. Words are parsed by the following parsing expression grammar: [[d-word]]Word:: (<> / !<> .)+ [[d-word-element]]WordElement:: +\+ . / + `'` (!`'` .)* `'` / + +"+ <>* +"+ / + <> / + <> / + <> [[d-quote-element]]QuoteElement:: +\+ ([+$`"\+] / ) / + <> / + <> / + <> / + ![+`"\+] . [[d-parameter]]Parameter:: +$+ [+@*#?-$!+ [:digit:]] / + +$+ <> / + +$+ <> [[d-portable-name]]PortableName:: ![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+ [[d-parameter-body]]ParameterBody:: +{+ <>? (<> / ParameterBody / +$+ ParameterBody / <> / <>) <>? <>? +}+ [[d-parameter-number]]ParameterNumber:: `#` ![`}+=:/%`] !([`-?#`] !`}`) [[d-parameter-name]]ParameterName:: [+@*#?-$!+] / + [[:alnum:] +_+]+ [[d-parameter-index]]ParameterIndex:: +[+ <> (+,+ ParameterIndexWord)? +]+ [[d-parameter-index-word]]ParameterIndexWord:: (<> / ![+"'],+] .)+ [[d-parameter-match]]ParameterMatch:: `:`? [`-+=?`] <> / + (`#` / `##` / `%` / `%%`) ParameterMatchWord / + (`:/` / `/` [`#%/`]?) <> (+/+ ParameterMatchWord)? [[d-parameter-match-word]]ParameterMatchWord:: (<> / ![+"'}+] .)* [[d-parameter-match-word-no-slash]]ParameterMatchWordNoSlash:: (<> / ![+"'/}+] .)* [[d-arithmetic]]Arithmetic:: `$((` <>* `))` [[d-arithmetic-body]]ArithmeticBody:: +\+ . / + <> / + <> / + <> / + +(+ ArithmeticBody +)+ / + ![+`()+] . [[d-command-substitution]]CommandSubstitution:: +$(+ <> +)+ / + +`+ <>* +`+ [[d-command-substitution-quoted]]CommandSubstitutionQuoted:: +$(+ <> +)+ / + +`+ <>* +`+ [[d-command-substitution-body]]CommandSubstitutionBody:: +\+ [+$`\+] / + !++`++ . [[d-command-substitution-body-quoted]]CommandSubstitutionBodyQuoted:: +\+ [+$`\`+] / + !++`++ . [[d-special-char]]SpecialChar:: [+|&;<>()`\"'+ [:blank:]] / The set of terminals of the grammar is the set of characters that can be handled on the environment in which the shell is run (a.k.a. execution character set), with the exception that the set does not contain the null character (`'\0'`). Strictly speaking, the definition above is not a complete parsing expression grammar because the rule for <> (<>) depends on <> which is a non-terminal of the syntax. [[classification]] === Token classification After a word token is delimited, the token may be further classified as an IO_NUMBER token, reserved word, name word, assignment word, or just normal word. Classification other than the normal word is applied only when applicable in the context in which the word appears. See link:syntax.html#tokens[Tokens and keywords] for the list of the reserved words (keywords) and the context in which a word may be recognized as a reserved word. A token is an IO_NUMBER token iff it is composed of digit characters only and immediately followed by +<+ or +>+. An assignment token is a token that starts with a name followed by +=+: [[d-assignment-word]]AssignmentWord:: <> <> [[d-assignment-prefix]]AssignmentPrefix:: <> +=+ [[d-name]]Name:: !\[[:digit:]] \[[:alnum:] +_+]+ [[comments]] === Comments A comment begins with `#` and continues up to (but not including) the next newline character. Comments are treated like a blank character and do not become part of a token. The initial `#` of a comment must appear as if it would otherwise be the first character of a word token; Other ++#++s are just treated as part of a word token. [[d-comment]]Comment:: `#` (! .)* [[syntax]] == Syntax After tokens have been delimited, the sequence of the tokens is parsed according to the context-free grammar defined below, where `*`, `+`, and `?` should be interpreted in the same manner as standard regular expression: [[d-complete-program]]CompleteProgram:: <>* | <> [[d-compound-list]]CompoundList:: <>* <> ({zwsp}(+;+ | +&+ | NL) <>)? [[d-and-or-list]]AndOrList:: <> ((+&&+ | +||+) <>* Pipeline)* [[d-pipeline]]Pipeline:: +!+? <> (+|+ <>* Command)* [[d-command]]Command:: <> <>* | + <> | + <> [[d-compound-command]]CompoundCommand:: <> | + <> | + <> | + <> | + <> | + <> | + <> | + <> [[d-subshell]]Subshell:: +(+ <> +)+ [[d-grouping]]Grouping:: +{+ <> +}+ [[d-if-command]]IfCommand:: +if+ <> +then+ CompoundList (+elif+ CompoundList +then+ CompoundList)* (+else+ CompoundList)? +fi+ [[d-for-command]]ForCommand:: +for+ <> ({zwsp}(<>* +in+ <>*)? (+;+ | NL) NL*)? +do+ <> +done+ [[d-while-command]]WhileCommand:: (+while+ | +until+) <> +do+ CompoundList +done+ [[d-case-command]]CaseCommand:: +case+ <> <>* +in+ NL* <>? +esac+ [[d-case-list]]CaseList:: <> (+;;+ <>* CaseList)? [[d-case-item]]CaseItem:: +(+? <> (+|+ Word)* +)+ <> [[d-double-bracket-command]]DoubleBracketCommand:: +[[+ <> +]]+ [[d-ors]]Ors:: <> (+||+ Ands)* [[d-ands]]Ands:: <> (+&&+ Nots)* [[d-nots]]Nots:: ++!++* <> [[d-primary]]Primary:: (+-b+ | +-c+ | +-d+ | +-e+ | +-f+ | +-G+ | +-g+ | +-h+ | +-k+ | +-L+ | +-N+ | +-n+ | +-O+ | +-o+ | +-p+ | +-r+ | +-S+ | +-s+ | +-t+ | +-u+ | +-w+ | +-x+ | +-z+) <> | + Word (+-ef+ | +-eq+ | +-ge+ | +-gt+ | +-le+ | +-lt+ | +-ne+ | +-nt+ | +-ot+ | +-veq+ | +-vge+ | +-vgt+ | +-vle+ | +-vlt+ | +-vne+ | +=+ | +==+ | +===+ | +=~+ | +!=+ | +!==+ | +<+ | +>+) Word | + +(+ <> +)+ | + Word [[d-function-command]]FunctionCommand:: +function+ <> (+(+ +)+)? <>* <> <>* [[d-function-definition]]FunctionDefinition:: <> +(+ +)+ <>* <> <>* [[d-simple-command]]SimpleCommand:: (<> | <>) SimpleCommand? | + <> (Word | <>)* [[d-assignment]]Assignment:: <> | + <>++(++ <>* (<> NL*)* +)+ [[d-redirection]]Redirection:: IO_NUMBER? <> <> | + IO_NUMBER? +<(+ <> +)+ | + IO_NUMBER? +>(+ CompleteProgram +)+ [[d-redirection-operator]]RedirectionOperator:: `<` | `<>` | `>` | `>|` | `>>` | `>>|` | `<&` | `>&` | `<<` | `<<-` | `<<<` [[d-nl]]NL:: In the rule for <>, <> tokens must not be +]]+. Additionally, if a Primary starts with a Word, it must not be any of the possible unary operators allowed in the rule. In the rule for <>, a <> token is accepted only when the token cannot be parsed as the first token of an <>. In the rule for <>, the +(+ token must immediately follow the <> token, without any blank characters in between. link:redir.html#here[Here-document] contents do not appear as part of the grammar above. They are parsed just after the newline (<>) token that follows the corresponding redirection operator. [[alias]] === Alias substitution Word tokens are subject to link:syntax.html#aliases[alias substitution]. - If a word is going to be parsed as a <> of a <>, the word is subjected to alias substitution of any kind (normal and global aliases). - If a word is the next token after the result of an alias substitution and the substitution string ends with a blank character, then the word is also subjected to alias substitution of any kind. - Other words are subjected to global alias substitution unless the shell is in the link:posix.html[POSIXly-correct mode]. Tokens that are classified as reserved words are not subject to alias substitution. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/index.txt.in000066400000000000000000000106321354143602500154430ustar00rootroot00000000000000= Yet another shell (yash) manual Yuki Watanabe v{yashversion}, :encoding: UTF-8 :lang: en //:title: Yet another shell (yash) manual :description: The table of contents for the yash manual. [[contents-list]] == Contents [role="list-group"] -- // (Replaced with generated contents list) // -- [[builtins]] == Built-ins The `*' mark indicates a special built-in and the `+' mark a semi-special built-in. (See link:builtin.html#types[Types of built-in commands]) [[alpha-order]] === All built-ins in alphabetic order [role="list-group"] - link:_dot.html[+.+ (dot)] * - link:_colon.html[+:+ (colon)] * - link:_test.html[+[+ (bracket)] - link:_alias.html[+alias+] + - link:_array.html[+array+] - link:_bg.html[+bg+] + - link:_bindkey.html[+bindkey+] + - link:_break.html[+break+] * - link:_cd.html[+cd+] + - link:_command.html[+command+] + - link:_complete.html[+complete+] + - link:_continue.html[+continue+] * - link:_dirs.html[+dirs+] + - link:_disown.html[+disown+] + - link:_echo.html[+echo+] - link:_eval.html[+eval+] * - link:_exec.html[+exec+] * - link:_exit.html[+exit+] * - link:_export.html[+export+] * - link:_false.html[+false+] + - link:_fc.html[+fc+] + - link:_fg.html[+fg+] + - link:_getopts.html[+getopts+] + - link:_hash.html[+hash+] + - link:_help.html[+help+] + - link:_history.html[+history+] + - link:_jobs.html[+jobs+] + - link:_kill.html[+kill+] + - link:_local.html[+local+] + - link:_popd.html[+popd+] + - link:_printf.html[+printf+] - link:_pushd.html[+pushd+] + - link:_pwd.html[+pwd+] + - link:_read.html[+read+] + - link:_readonly.html[+readonly+] * - link:_return.html[+return+] * - link:_set.html[+set+] * - link:_shift.html[+shift+] * - link:_suspend.html[+suspend+] + - link:_test.html[+test+] - link:_times.html[+times+] * - link:_trap.html[+trap+] * - link:_true.html[+true+] + - link:_type.html[+type+] + - link:_typeset.html[+typeset+] + - link:_ulimit.html[+ulimit+] + - link:_umask.html[+umask+] + - link:_unalias.html[+unalias+] + - link:_unset.html[+unset+] * - link:_wait.html[+wait+] + [[groups]] === Categorized list of built-ins [[g-control]] ==== Execution control commands [role="list-group"] - link:_eval.html[+eval+] * - link:_dot.html[+.+ (dot)] * - link:_exec.html[+exec+] * - link:_command.html[+command+] + - link:_hash.html[+hash+] - link:_break.html[+break+] * - link:_continue.html[+continue+] * - link:_return.html[+return+] * - link:_suspend.html[+suspend+] - link:_exit.html[+exit+] * [[g-environ]] ==== Command execution environment [role="list-group"] - link:_set.html[+set+] * - link:_ulimit.html[+ulimit+] - link:_umask.html[+umask+] + - link:_trap.html[+trap+] * - link:_cd.html[+cd+] + - link:_pwd.html[+pwd+] + - link:_times.html[+times+] * [[g-job]] ==== Job control and signalling [role="list-group"] - link:_jobs.html[+jobs+] + - link:_fg.html[+fg+] + - link:_bg.html[+bg+] + - link:_wait.html[+wait+] + - link:_disown.html[+disown+] - link:_kill.html[+kill+] + - link:_trap.html[+trap+] * [[g-variable]] ==== Parameters and variables [role="list-group"] - link:_typeset.html[+typeset+] - link:_export.html[+export+] * - link:_local.html[+local+] + - link:_readonly.html[+readonly+] * - link:_array.html[+array+] - link:_set.html[+set+] * - link:_shift.html[+shift+] * - link:_read.html[+read+] + - link:_getopts.html[+getopts+] + - link:_unset.html[+unset+] * [[g-directory]] ==== Working directory [role="list-group"] - link:_cd.html[+cd+] + - link:_pwd.html[+pwd+] + - link:_pushd.html[+pushd+] - link:_popd.html[+popd+] - link:_dirs.html[+dirs+] [[g-alias]] ==== Aliases [role="list-group"] - link:_alias.html[+alias+] + - link:_unalias.html[+unalias+] + [[g-history]] ==== Command history [role="list-group"] - link:_fc.html[+fc+] + - link:_history.html[+history+] [[g-print]] ==== Printing strings [role="list-group"] - link:_echo.html[+echo+] - link:_printf.html[+printf+] [[g-lineedit]] ==== Line-editing [role="list-group"] - link:_bindkey.html[+bindkey+] - link:_complete.html[+complete+] [[g-misc]] ==== Miscellaneous [role="list-group"] - link:_help.html[+help+] - link:_colon.html[+:+ (colon)] * - link:_true.html[+true+] + - link:_false.html[+false+] + - link:_test.html[+[+ (bracket), +test+] - link:_type.html[+type+] // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/interact.txt000066400000000000000000000303411354143602500155370ustar00rootroot00000000000000= Interactive mode :encoding: UTF-8 :lang: en //:title: Yash manual - Interactive mode :description: This page describes the interactive mode of the yash shell. The dfn:[interactive mode] is a mode of the shell intended for direct interaction with a user. If yash is in the interactive mode, it is called an dfn:[interactive shell]. Whether a shell runs in the interactive mode or not is determined in the link:invoke.html[invocation of the shell]. After the shell has started up, the interactive mode cannot be switched on or off. When the shell is interactive: - link:invoke.html#init[Initialization] scripts are executed during invocation. - The shell link:interact.html#mailcheck[checks for mail] and prints a command <> when it reads a command. Job status changes are also reported if link:job.html[job control] is active. link:lineedit.html[Line-editing] may be used depending on the capability of the terminal. - Commands executed are automatically registered in command <>. - If a command executed by the shell is killed by a signal other than SIGINT and SIGPIPE, the shell reports the fact to the standard error. - The filename token is subject to link:expand.html#glob[pathname expansion] in link:redir.html#file[file redirection]. - The shell does not exit when it encounters a syntax or expansion error during command execution. (cf. link:exec.html#exit[Termination of the shell]) - The shell does not exit when it receives the SIGINT, SIGTERM, or SIGQUIT signal. - A signal handler can be changed by the link:_trap.html[trap built-in] even if the handler had been set to ``ignore'' when the shell was invoked. - The value of the link:params.html#sp-hyphen[+-+ special parameter] contains +i+. - The shell's locale reflects the value of the link:params.html#sv-lc_ctype[+LC_CTYPE+ variable] whenever the value is changed (if the shell is not in the link:posix.html[POSIXly-correct mode]). - Commands are executed even when the link:_set.html#so-exec[exec option] is off. - The link:_set.html#so-ignoreeof[ignore-eof option] takes effect when enabled. - When the shell reaches the end of input or the link:_exit.html[exit built-in] is executed, the shell checks if there is any stopped link:job.html[job]. If so, the shell prints a warning and does not actually exit. - The link:_suspend.html[suspend built-in] by default cannot stop the shell if it is a session leader. - The shell does not exit when the link:_dot.html[dot built-in] fails to find a script file to read. - The shell does not exit when the link:_exec.html[exec built-in] fails to execute a command (if not in the link:posix.html[POSIXly-correct mode]). - When a job finished for which the link:_wait.html[wait built-in] has been waiting, the fact is reported (only if link:job.html[job control] is active and not in the link:posix.html[POSIXly-correct mode]). - A prompt is printed when the link:_read.html[read built-in] reads a second or following line. [[prompt]] == Prompts The interactive shell prints a dfn:[prompt] just before it reads a command. The contents of the prompt is specified by the value of the link:params.html#sv-ps1[+PS1+] and link:params.html#sv-ps2[+PS2+] variables. The former is used for reading the first line of the command and the latter for other lines. When the prompt is printed, the variable value is subjected to link:expand.html#params[parameter expansion], link:expand.html#cmdsub[command substitution], and link:expand.html#arith[arithmetic expansion] (but note that the POSIX standard requires parameter expansion only). The result of the expansion is parsed by the rules below to make the actual prompt string, which is printed to the standard error. In the link:posix.html[POSIXly-correct mode], each exclamation mark (+!+) in the string is substituted with the command <> number of the command that is being input. Two adjacent exclamation marks (+!!+) are printed as a single exclamation. Other characters are printed intact. If the shell is not in the POSIXly-command mode, the following notations can be used to format the prompt string. Notations are replaced with the strings designated in the list below. Characters that are not interpreted as notations are printed intact. +\a+:: Bell character (ASCII code: 7) +\e+:: Escape character (ASCII code: 27) +\j+:: The number of link:job.html[jobs] in the shell. +\n+:: Newline character (ASCII code: 10) +\r+:: Carriage return character (ASCII code: 13) +\!+:: The command <> number of the command that is being input +\$+:: +#+ if the shell's effective user ID is 0; +$+ otherwise. +\\+:: Backslash +\[+:: +\]+:: These two notations can surround part of the prompt string that is not visible on the terminal. The surrounded part is ignored when the shell counts the number of characters that is displayed on the terminal, thus making characters correctly aligned on the terminal when the prompt string contains special invisible characters. +\f{{fontspecs}}.+:: When link:lineedit.html[line-editing] is active, this notation is replaced with special characters to change font styles on the terminal if the terminal is capable of it. If line-editing is inactive or the terminal is incapable of changing font styles, this notation is silently ignored. One or more of the following can be used for {{fontspecs}}: + -- +k+:: Change font color to black +r+:: Change font color to red +g+:: Change font color to green +y+:: Change font color to yellow +b+:: Change font color to blue +m+:: Change font color to magenta +c+:: Change font color to cyan +w+:: Change font color to white +K+:: Change background color to black +R+:: Change background color to red +G+:: Change background color to green +Y+:: Change background color to yellow +B+:: Change background color to blue +M+:: Change background color to magenta +C+:: Change background color to cyan +W+:: Change background color to white +t+:: Make font color or background brighter (can only be used just after one of the characters above) +d+:: Change font and background colors to normal +s+:: Make font standout +u+:: Make font underlined +v+:: Make font and background colors reversed +b+:: Make font blink +i+:: Make font dim +o+:: Make font bold +x+:: Make font invisible +D+:: Make color and style normal -- + The actual colors of font and background are defined by the terminal. Different terminals may use different colors. In addition to the normal prompt, a prompt string can be displayed to the right of the cursor if link:lineedit.html[line-editing] is active. Those prompts are called dfn:[right prompts]. The contents of right prompts are defined by the value of the link:params.html#sv-ps1r[+PS1R+] and link:params.html#sv-ps2r[+PS2R+] variables, each corresponding to the link:params.html#sv-ps1[+PS1+] and link:params.html#sv-ps2[+PS2+] variables. Using the above-said notations, the font style of command strings the user inputs can be changed as well as that of prompts. The font style of command strings is defined by the value of the link:params.html#sv-ps1s[+PS1S+] and link:params.html#sv-ps2s[+PS2S+] variables, each corresponding to the link:params.html#sv-ps1[+PS1+] and link:params.html#sv-ps2[+PS2+] variables. The value can contain the +\f{{fontspecs}}.+ notation only. When the shell is not in the link:posix.html[POSIXly-correct mode], the prompt variables can be defined with a name prefixed with +YASH_+ (e.g. link:params.html#sv-yash_ps1[+YASH_PS1+]). This allows using a different prompt string than that in the POSIXly-correct mode. When the shell is not in the link:posix.html[POSIXly-correct mode], the value of the link:params.html#sv-prompt_command[+PROMPT_COMMAND+ variable] is executed before each prompt. [[history]] == Command history dfn:[Command history] is a feature of the shell that remembers executed commands to allow re-executing them later. Commands executed in the interactive mode are automatically saved in the command history. Saved commands can be edited and re-executed using link:lineedit.html[line-editing] and the link:_fc.html[fc] and link:_history.html[history] built-ins. Commands are saved line by line. Lines that do not contain any non-whitespace characters are not saved in the history. Lines that start with whitespaces are not saved when the link:_set.html#so-histspace[hist-space option] is on. Command history is saved in a file. When history is first used after an interactive shell was started, the shell opens a file to save history in. The filename is specified by the value of the link:params.html#sv-histfile[+HISTFILE+ variable]. If the file contains history data when opened, the data is restored to the shell's history. The file contents are updated in real time as the user inputs commands into the shell. If the +HISTFILE+ variable is not set or the file cannot be opened successfully, history is not saved in the file, but the history feature will be functional in all other respects. The number of commands saved in history is specified by the value of the link:params.html#sv-histsize[+HISTSIZE+ variable]. The shell automatically removes old history data so that the number of saved commands does not exceed the value. If the +HISTSIZE+ variable is not set or its value is not a natural number, 500 items will be saved in history. The shell looks at the value of the +HISTFILE+ and +HISTSIZE+ variables only when the history feature is first used after the shell was started. ``The history feature is used'' when: - the link:_fc.html[fc] or link:_history.html[history] built-in is executed, - link:lineedit.html[line-editing] is used (regardless of whether or not history data is recalled in line-editing), or - a command is input to the shell Therefore, the variables should be set in link:invoke.html#init[initialization] scripts. When more than one instance of yash shares a single history file, all the shells use the same history data. As a result, commands that have been executed by a shell instance can be recalled on another shell instance. Shells sharing the same history should have the same +HISTSIZE+ value so that they manipulate history data properly. Yash's history data file has its own format that is incompatible with other kinds of shells. The link:params.html#sv-histrmdup[+HISTRMDUP+ variable] can be set to remove duplicate history items. [[mailcheck]] == Mail checking An interactive shell can notify receipt of email. The shell periodically checks the modification date/time of a file specified by the user. If the file has been modified since the previous check, the shell prints a notification message (except when the shell is not in the link:posix.html[POSIXly-correct mode] and the file is empty). By specifying a mailbox file to be checked, the shell will print a message when the file has been modified, that is, some mail has been received. Check is done just before the shell prints a command line prompt. The interval of checks can be specified by the link:params.html#sv-mailcheck[+MAILCHECK+ variable] in seconds. If the variable value is 0, check is done before every prompt. If the variable value is not a non-negative integer, no checks are done. The file whose modification time is checked is specified by the link:params.html#sv-mail[+MAIL+ variable]. The variable value should be set to the pathname of the file. If you want to check more than one file or customize the notification message, you can set the link:params.html#sv-mailpath[+MAILPATH+ variable] instead of the +MAIL+ variable. When the +MAILPATH+ variable is set, the +MAIL+ variable is ignored. The value of the +MAILPATH+ variable should be set to one or more colon-separated pathnames of files to be checked. Each pathname can be followed by a percent sign (+%+) and a custom notification message, which is printed when the corresponding file has been modified. If the pathname contains a percent sign, it should be link:syntax.html#quotes[quoted] by a backslash. The specified message is subject to link:expand.html#params[parameter expansion]. For example, if the value of the +MAILPATH+ variable is `/foo/mail%New mail!:/bar/mailbox%You've got mail:/baz/mail\%data`, the shell will print - `New mail!` when the file /foo/mail has been modified - `You've got mail` when the file /bar/mailbox has been modified - the default message when the file /baz/mail%data has been modified. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/intro.txt000066400000000000000000000023251354143602500150620ustar00rootroot00000000000000= Introduction :encoding: UTF-8 :lang: en //:title: Yash manual - Introduction :description: This is the introduction page of the yash manual. dfn:[Yet another shell] (yash) is a command line shell for UNIX-like operating systems. The shell conforms to the POSIX.1-2008 standard (for the most parts), and actually is more conforming than other POSIX-conforming shells. Moreover, it has many features that are used for interactive use, such as command history and command line editing. This program can be freely modified and redistributed under the terms of http://www.gnu.org/licenses/gpl.html[GNU General Public License] (Version 2). *Use of this program is all at your own risk. There is no warranty and the author is not responsible for any consequences caused by use of this program.* This manual can be freely modified and redistributed under the terms of http://creativecommons.org/licenses/by-sa/2.1/jp/[Creative Commons Attribution-ShareAlike 2.1 Japan]. Yash is developed and maintained by 渡邊裕貴 (WATANABE Yuki) aka Magicant. http://osdn.jp/projects/yash/[Yash development project] and http://yash.osdn.jp/[Yash's homepage] are hosted by http://osdn.jp/[OSDN]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/invoke.txt000066400000000000000000000124541354143602500152260ustar00rootroot00000000000000= Invocation :encoding: UTF-8 :lang: en //:title: Yash manual - Invocation :description: This page describes how yash is initialized when invoked. When invoked as a program, yash performs the predefined initialization steps and repeatedly reads and executed commands. Command line arguments given in the invocation determines how the shell initializes itself and executes commands. [[arguments]] == Command line arguments The syntax of command line arguments for yash conforms to POSIX. As defined in POSIX, arguments are separated into dfn:[options] and dfn:[operands]. For more detailed explanation about options and operands, see link:builtin.html#argsyntax[Command argument syntax]. All options must come before operands. The interpretation of operands depends on options specified. When you specify the +-c+ (+--cmdline+) option, you must give at least one operand. The shell interprets and executes the first operand as a command string. The second operand, if any, is used to initialize the link:params.html#sp-zero[+0+ special parameter]. The other operands, if any, are used to initialize the link:params.html#positional[positional parameters]. When the +-c+ (+--cmdline+) option is specified, the shell does not read any file or the standard input (unless the link:_dot.html[dot built-in] is used). If you specify the +-s+ (+--stdin+) option, the shell reads the standard input, interprets the input as commands, and executes them. All the operands given are used to initialize the link:params.html#positional[positional parameters]. The link:params.html#sp-zero[+0+ special parameter] is initialized to the name the shell is invoked as. If you specify neither the +-c+ (+--cmdline+) nor +-s+ (+--stdin+) option, the shell reads a file, interprets the file contents as commands, and executes them. The first operand specifies the pathname of the file. The remaining operands are used to initialize the link:params.html#positional[positional parameters]. If you do not give any operands, the shell reads the standard input as if the +-s+ (+--stdin+) option is specified. You cannot use both the +-c+ (+--cmdline+) and +-s+ (+--stdin+) options at a time. If you specify either the +--help+ or +-V+ (+--version+) option, the shell never performs the usual initialization or command execution. Instead, it just prints brief usage (for +--help+) or version information (for +-V+ and +--version+). If the +-V+ (+--version+) option is accompanied by the +-v+ (+--verbose+) option, the shell prints a list of the available optional features as well. If you specify the +-i+ (+--interactive+) option, the shell goes into the link:interact.html[interactive mode]. If you specify the `+i` (`++interactive`) option, conversely, the shell never goes into the interactive mode. If you specify the +-l+ (+--login+) option, the shell behaves as a login shell. The +--noprofile+, +--norcfile+, +--profile+, and +--rcfile+ options determine how the shell is initialized (see below for details). In addition to the options described above, you can specify options that can be specified to the link:_set.html[set built-in]. If the first operand is +-+ and the options and the operands are not separated by +--+, the first operand is ignored. [[init]] == Initialization of yash Yash initializes itself as follows: . Yash first parses the name it was invoked as. If the name starts with +-+, the shell behaves as a login shell. If the name is +sh+ (including names such as +/bin/sh+), the shell goes into the link:posix.html[POSIXly-correct mode]. . If no operands are given and the standard input and standard error are both connected to a terminal, the shell goes into the link:interact.html[interactive mode] unless the `+i` (`++interactive`) option is specified. . link:job.html[Job control] is automatically enabled in an interactive shell unless the `+m` (`++monitor`) option is specified. . Yash reads and executes commands from the following files (unless the real and effective user IDs of the shell process are different or the real and effective group IDs of the shell process are different): .. If it is behaving as a login shell, the shell reads the file specified by the +--profile={{filename}}+ option unless the +--noprofile+ option is specified or the shell is in the link:posix.html[POSIXly-correct mode]. + If the +--profile={{filename}}+ option is not specified, the shell reads link:expand.html#tilde[~]/.yash_profile as a default. .. If in the interactive mode, the shell reads the file specified by the +--rcfile={{filename}}+ option unless the +--norcfile+ option is specified. + If the +--rcfile={{filename}}+ option is not specified, the shell instead reads the following files: - If not in the POSIXly-correct mode, the shell reads link:expand.html#tilde[~]/.yashrc. If it cannot be read, the shell searches link:params.html#sv-yash_loadpath[+YASH_LOADPATH+] for a file named initialization/default. - If in the POSIXly-correct mode, the shell performs link:expand.html[parameter expansion] on the value of the link:params.html#sv-env[+ENV+ environment variable] and treats the expansion result as the name of the file to read. [NOTE] Yash never automatically reads /etc/profile, /etc/yashrc, nor link:expand.html#tilde[~]/.profile. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/ja/000077500000000000000000000000001354143602500135565ustar00rootroot00000000000000yash-2.49/doc/ja/Makefile.in000066400000000000000000000133721354143602500156310ustar00rootroot00000000000000# Makefile.in for the documentation of yash # (C) 2007-2018 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # NOTE: In this Makefile it is assumed that the make implementation allows the # use of hyphens in target names. This means that there may be a strictly # POSIX-conforming implementation of make that rejects this Makefile. I have # never seen such an implementation but if you know of one please let me know. .POSIX: .SUFFIXES: .txt .1 .xml .html @MAKE_SHELL@ topdir = ../.. subdir = doc/$(LOCALE) recdirs = ASCIIDOC = @ASCIIDOC@ ASCIIDOCFLAGS = @ASCIIDOCFLAGS@ ASCIIDOCATTRS = @ASCIIDOCATTRS@ A2X = @A2X@ A2XFLAGS = @A2XFLAGS@ INSTALL = @INSTALL@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_DIR = @INSTALL_DIR@ VERSION = @VERSION@ LOCALE = ja MANTXT = yash.txt INDEXTXT = index.txt # MAINTXTS must be in the contents order MAINTXTS = intro.txt invoke.txt syntax.txt params.txt expand.txt pattern.txt redir.txt exec.txt interact.txt job.txt builtin.txt lineedit.txt posix.txt faq.txt fgrammar.txt # BUILTINTXTS must be in the alphabetic order BUILTINTXTS = _alias.txt _array.txt _bg.txt _bindkey.txt _break.txt _cd.txt _colon.txt _command.txt _complete.txt _continue.txt _dirs.txt _disown.txt _dot.txt _echo.txt _eval.txt _exec.txt _exit.txt _export.txt _false.txt _fc.txt _fg.txt _getopts.txt _hash.txt _help.txt _history.txt _jobs.txt _kill.txt _local.txt _popd.txt _printf.txt _pushd.txt _pwd.txt _read.txt _readonly.txt _return.txt _set.txt _shift.txt _suspend.txt _test.txt _times.txt _trap.txt _true.txt _type.txt _typeset.txt _ulimit.txt _umask.txt _unalias.txt _unset.txt _wait.txt # CONTENTSTXTS must be in the contents order CONTENTSTXTS = $(MAINTXTS) $(BUILTINTXTS) TXTS = $(MANTXT) $(INDEXTXT) $(CONTENTSTXTS) MANFILE = $(MANTXT:.txt=.1) INDEXHTML = $(INDEXTXT:.txt=.html) HTMLFILES = $(INDEXHTML) $(CONTENTSTXTS:.txt=.html) HTMLAUXFILES = asciidoc.css TARGETS = $(MANFILE) $(HTMLFILES) CONFFILE = asciidoc.conf DESTDIR = prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datarootdir = @datarootdir@ datadir = @datadir@ yashdatadir = $(datadir)/$(TARGET) localedir = @localedir@ mandir = @mandir@ man1dir = $(mandir)/man1 docdir = @docdir@ htmldir = @htmldir@ localemandir = $(mandir)/$(LOCALE) localeman1dir = $(localemandir)/man1 localedocdir = $(docdir)/$(LOCALE) localehtmldir = $(htmldir)/$(LOCALE) default-rec: default all-rec: all man-rec: man html-rec: html default: man html all: $(TARGETS) man: $(MANFILE) html: $(HTMLFILES) .txt.1: $(A2X) -d manpage -f manpage -L $(A2XFLAGS) $(ASCIIDOCATTRS) $< .txt.html: $(ASCIIDOC) -b html -d article $(ASCIIDOCFLAGS) $(ASCIIDOCATTRS) $< $(MANTXT): ../makeyash.sh $(MANTXT).in Makefile.in $(SHELL) ../makeyash.sh $(CONTENTSTXTS) <$@.in >$@ $(INDEXTXT): ../makeindex.sh $(INDEXTXT).in $(MAINTXTS) Makefile.in $(SHELL) ../makeindex.sh $(MAINTXTS) <$@.in >$@ $(MANFILE): $(MANTXT) $(CONTENTSTXTS) $(CONFFILE) $(HTMLFILES): $(CONFFILE) # update date in index.html when any contents file has been modified $(INDEXHTML): $(CONTENTSTXTS) install-rec: install install-data-rec: install-data install-html-rec: install-html installdirs-rec: installdirs installdirs-data-rec: installdirs-data installdirs-html-rec: installdirs-html uninstall-rec: uninstall uninstall-data-rec: uninstall-data INSTALLDIRS = $(DESTDIR)$(localeman1dir) $(DESTDIR)$(localehtmldir) install: install-data install-data: man installdirs-data $(INSTALL_DATA) $(MANFILE) $(DESTDIR)$(localeman1dir) install-html: html installdirs-html $(INSTALL_DATA) $(HTMLFILES) $(HTMLAUXFILES) $(DESTDIR)$(localehtmldir) installdirs: installdirs-data installdirs-data: $(DESTDIR)$(localeman1dir) installdirs-html: $(DESTDIR)$(localehtmldir) $(INSTALLDIRS): $(INSTALL_DIR) $@ uninstall: uninstall-data uninstall-data: rm -f $(DESTDIR)$(localeman1dir)/$(MANFILE) (if cd $(DESTDIR)$(localehtmldir); then rm -f $(HTMLFILES) $(HTMLAUXFILES); fi) -rmdir $(DESTDIR)$(localehtmldir) DISTFILES = $(TARGETS) $(HTMLAUXFILES) $(TXTS) $(MANTXT).in $(INDEXTXT).in Makefile.in $(CONFFILE) distfiles: $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(topdir)/$(DISTTARGETDIR) mostlyclean: _mostlyclean _mostlyclean: clean: _clean _clean: _mostlyclean distclean: _distclean _distclean: _clean rm -fr Makefile maintainer-clean: _maintainer-clean _maintainer-clean: _distclean rm -fr $(TARGETS) $(MANTXT) $(INDEXTXT) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) default-rec all-rec man-rec html-rec install-rec install-data-rec install-html-rec installdirs-rec installdirs-data-rec installdirs-html-rec uninstall-rec uninstall-data-rec mostlyclean clean distclean maintainer-clean: @+for dir in $(recdirs); do (cd $$dir && $(MAKE) $@) done .PHONY: default-rec all-rec man-rec html-rec default all man html install-rec install-data-rec install-html-rec installdirs-rec installdirs-data-rec installdirs-html-rec uninstall-rec uninstall-data-rec install install-data install-html installdirs installdirs-data installdirs-html uninstall uninstall-data distfiles copy-distfiles mostlyclean _mostlyclean clean _clean distclean _distclean maintainer-clean _maintainer-clean yash-2.49/doc/ja/_alias.txt000066400000000000000000000042151354143602500155510ustar00rootroot00000000000000= Alias 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Alias 組込みコマンド dfn:[Alias 組込みコマンド]は{zwsp}link:syntax.html#aliases[エイリアス]を設定・表示します。 [[syntax]] == 構文 - +alias [-gp] [{{エイリアス名}}[={{値}}]...]+ [[description]] == 説明 Alias コマンドは{zwsp}link:syntax.html#aliases[エイリアス]をオペランドに従って設定または表示します。表示は、コマンド (の一部) として解釈可能な形式で標準出力に出力します。オペランドを一つも与えない場合、alias コマンドは現在設定されている全てのエイリアスを表示します。 [[options]] == オプション +-g+:: +--global+:: このオプションを指定した場合、設定するエイリアスはグローバルエイリアスになります。このオプションを指定しない場合、設定するエイリアスは通常のエイリアスになります。 +-p+:: +--prefix+:: このオプションは表示の書式を選択します。このオプションを指定した場合、alias コマンドとそのコマンドライン引数全てを表示します。このオプションを指定しない場合、alias コマンドに渡すオペランドだけを表示します。 [[operands]] == オペランド {{エイリアス名}}:: 表示するエイリアスの名前です。 {{エイリアス名}}={{値}}:: 設定するエイリアスの名前とその内容です。 [[exitstatus]] == 終了ステータス エラーがない限り alias コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Yash でエイリアスの名前として使えない文字は、空白文字・タブ・改行、および +=$<>\'"`;&|()#+ の各文字です。エイリアスの内容にはすべての文字が使えます。 Alias コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX にはオプションに関する規定はありません。よってオプションは link:posix.html[POSIX 準拠モード]では使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_array.txt000066400000000000000000000054541354143602500156040ustar00rootroot00000000000000= Array 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Array 組込みコマンド dfn:[Array 組込みコマンド]は{zwsp}link:params.html#arrays[配列]の表示や操作を行います。 [[syntax]] == 構文 - +array+ - +array {{配列名}} [{{値}}...]+ - +array -d {{配列名}} [{{インデックス}}...]+ - +array -i {{配列名}} {{インデックス}} [{{値}}...]+ - +array -s {{配列名}} {{インデックス}} {{値}}+ [[description]] == 説明 オプションもオペランドも指定せずに実行すると、array コマンドは全ての配列の定義を (コマンドとして解釈可能な形式で) 標準出力に出力します。 オプションを指定せずに{{配列名}}と{{値}}を与えて実行すると、array コマンドはその配列の内容を指定された値に設定します。 +-d+ (+--delete+) オプションを指定して実行すると、array コマンドは指定した配列の指定したインデックスにある要素を削除します。配列の要素数は削除した要素の分だけ少なくなります。存在しない要素のインデックスを指定したときは無視します。 +-i+ (+--insert+) オプションを指定して実行すると、array コマンドは指定した配列の指定したインデックスにある要素の直後に指定した値を要素として追加します。配列の要素数は追加した要素の分だけ増えます。{{インデックス}} として 0 を指定すると配列の先頭に要素を追加します。{{インデックス}}として配列の要素数より大きな数を指定すると配列の末尾に要素を追加します。 +-s+ (+--set+) オプションを指定して実行すると、array コマンドは指定した配列の指定したインデックスにある要素の値を指定した値に変更します。 [[options]] == オプション +-d+:: +--delete+:: 配列の要素を削除します。 +-i+:: +--insert+:: 配列に要素を挿入します。 +-s+:: +--set+:: 配列の要素を変更します。 [[operands]] == オペランド {{配列名}}:: 表示または操作する配列の名前です。 {{インデックス}}:: 配列の要素を指定する自然数です。インデックスは最初の要素から順に 1, 2, 3, … と割り振られます。 {{値}}:: 配列の要素となる文字列です。 [[exitstatus]] == 終了ステータス エラーがない限り array コマンドの終了ステータスは 0 です。 [[notes]] == 補足 POSIX には array コマンドに関する規定はありません。 +array {{配列名}} [{{値}}...]+ の形式の array コマンドは変数代入を用いて +{{配列名}}=({{値}}...)+ と書くこともできます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_bg.txt000066400000000000000000000030041354143602500150430ustar00rootroot00000000000000= Bg 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Bg 組込みコマンド dfn:[Bg 組込みコマンド]はジョブをバックグラウンドで実行します。 [[syntax]] == 構文 - +bg [{{ジョブ}}...]+ [[description]] == 説明 Bg コマンドはジョブをバックグラウンドで実行します。ジョブには SIGCONT シグナルが送られ、ジョブが停止している場合は再開されます。 ジョブの実行を再開する前に bg コマンドはジョブの名前を標準出力に出力します。 Bg コマンドは{zwsp}link:job.html[ジョブ制御]が有効な時しか使えません。 [[operands]] == オペランド {{ジョブ}}:: 実行するジョブの{zwsp}link:job.html#jobid[ジョブ ID]。 + ジョブを複数指定することもできます。何も指定しないと現在のジョブを実行します。 + 非 link:posix.html[POSIX 準拠モード]ではジョブ ID の先頭の +%+ は省略できます。 [[exitstatus]] == 終了ステータス エラーがない限り bg コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Bg コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX は指定したジョブが既に実行中の場合は bg コマンドは何もしないと規定していますが、yash の bg コマンドはジョブが実行中かどうかにかかわらず SIGCONT シグナルを送信します。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_bindkey.txt000066400000000000000000000046231354143602500161100ustar00rootroot00000000000000= Bindkey 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Bindkey 組込みコマンド dfn:[Bindkey 組込みコマンド]は{zwsp}link:lineedit.html[行編集]におけるキーバインド設定を表示・設定します。 [[syntax]] == 構文 - +bindkey -aev [{{キー}} [{{コマンド}}]]+ - +bindkey -l+ [[description]] == 説明 +-l+ (+--list+) オプションを付けて実行すると、bindkey コマンドはキーバインド設定で利用可能な{zwsp}link:lineedit.html#commands[行編集コマンド]の一覧を標準出力に出力します。 他のオプションを付けて実行すると、bindkey コマンドはキーバインド設定の表示または設定を行います。 - オペランドとして{{キー}}・{{コマンド}}を与えない場合、現在のキーバインド設定の内容を (コマンドとして解釈可能な形式で) 標準出力に出力します。 - {{キー}}のみを与えると、そのキーに対する現在の設定だけを出力します。 - {{キー}}と{{コマンド}}を両方与えると、そのキーを入力したときに実行するコマンドを指定したコマンドに設定します。 [[options]] == オプション +-a+:: +--vi-command+:: Vi 風編集モードのコマンドモードにおけるキーバインドを表示・設定します。 +-e+:: +--emacs+:: Emacs 風編集モードにおけるキーバインドを表示・設定します。 +-v+:: +--vi-insert+:: Vi 風編集モードの挿入モードにおけるキーバインドを表示・設定します。 [[operands]] == オペランド {{キー}}:: 表示・設定する対象のキー入力シーケンスです。このオペランドの値にはバックスラッシュで始まる{zwsp}link:lineedit.html#escape[エスケープシーケンス]が利用できます。 {{コマンド}}:: 設定する{zwsp}link:lineedit.html#commands[行編集コマンド]です。ハイフン一つ (+-+) を指定すると、指定した{{キー}}に対する設定を削除します。 [[exitstatus]] == 終了ステータス エラーがない限り bindkey コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Bindkey コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では bindkey コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_break.txt000066400000000000000000000047121354143602500155460ustar00rootroot00000000000000= Break 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Break 組込みコマンド dfn:[Break 組込みコマンド]は実行中のループを中断します。 [[syntax]] == 構文 - +break [{{深さ}}]+ - +break -i+ [[description]] == 説明 +-i+ (+--iteration+) オプションを付けずに実行すると、break コマンドは現在実行中の link:syntax.html#for[for ループ]または link:syntax.html#while-until[while ループ]または link:syntax.html#while-until[until ループ]を中断します。多重ループの中で実行した場合、内側から数えて{{深さ}}番目のループを中断します。{{深さ}}が指定されていないときは、最も内側のループを中断します ({{深さ}} = 1)。指定された{{深さ}}が実際に実行している多重ループの深さより大きい場合は最も外側のループを中断します。 +-i+ (+--iteration+) オプションを付けて実行すると、break コマンドは現在実行中の{zwsp}link:_eval.html#iter[反復実行]を中断します。 [[options]] == オプション +-i+:: +--iteration+:: ループではなく反復実行を中断します。 [[operands]] == オペランド {{nest}}:: 内側から何番目のループを中断するのかを指定する 1 以上の自然数です。 [[exitstatus]] == 終了ステータス ループの中断に成功すると終了ステータスは 0 です。反復実行の中断に成功すると break コマンドの直前に実行されたコマンドの終了ステータスが break コマンドの終了ステータスになります。 [[notes]] == 補足 Break コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX には +-i+ (+--interact+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 POSIX では、break コマンドを構文的に取り囲んでいないループの扱いを規定していません。例えば以下のような場合が該当します: - ループの中で{zwsp}link:exec.html#function[関数]が実行され、その中で break コマンドが実行される - ループの実行中に{zwsp}link:_trap.html[トラップ]が実行され、その中で break コマンドが実行される Yash では、このようなループは break の対象に出来ません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_cd.txt000066400000000000000000000104231354143602500150440ustar00rootroot00000000000000= Cd 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Cd 組込みコマンド dfn:[Cd 組込みコマンド]はシェルの作業ディレクトリを変更します。 [[syntax]] == 構文 - +cd [-L|-P] [{{ディレクトリ}}]+ [[description]] == 説明 Cd コマンドはシェルの作業ディレクトリをオペランドで指定したディレクトリに変更します。 新しい作業ディレクトリに応じて link:params.html#sv-pwd[+PWD+ 変数]の値が再設定されるとともに、前の +PWD+ 変数の値が link:params.html#sv-oldpwd[+OLDPWD+ 変数]に設定されます。新しい +PWD+ の値は +.+ や +..+ のディレクトリ成分を含みません (link:posix.html[POSIX 準拠モード]で新しいパスが +/..+ で始まる場合を除く)。 指定した{{ディレクトリ}}が相対パスの場合 (最初が +.+ または +..+ で始まるものを除く)、{zwsp}link:exec.html#search[コマンドの検索]における link:params.html#sv-path[+PATH+ 変数]の検索と同様にして、{zwsp}link:params.html#sv-cdpath[+CDPATH+ 変数]の値にあるコロンで区切った各ディレクトリ内に指定した{{ディレクトリ}}があるかどうか調べます。ディレクトリが見つかった場合は、そのディレクトリが新しい作業ディレクトリになります。見つからなかった場合は、{{ディレクトリ}}は現在の作業ディレクトリからの相対パスとなります。 +CDPATH+ 変数の検索で新しい作業ディレクトリが見つかった場合または{{ディレクトリ}}として +-+ が指定された場合は新しい作業ディレクトリのパスを標準出力に出力します。 作業ディレクトリの変更に成功した場合、{zwsp}link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+ 変数]が設定されていればその値がコマンドとして解釈・実行されます (非 link:posix.html[POSIX 準拠モード]時)。 [[options]] == オプション +-L+:: +--logical+:: ディレクトリパスに含まれるシンボリックリンクを解決せずに新しい作業ディレクトリを決定します。新しい +PWD+ 変数の値にはシンボリックリンクになっているパス名コンポーネントがそのまま残ります。 +-P+:: +--physical+:: ディレクトリパスに含まれるシンボリックリンクを解決します。新しい +PWD+ 変数の値はシンボリックリンクを含みません。 +--default-directory={{ディレクトリ}}+:: {{ディレクトリ}}オペランドが与えられていない場合は、代わりにこのオプションで指定した{{ディレクトリ}}を新しい作業ディレクトリとします。 +-L+ (+--logical+) オプションと +-P+ (+--physical+) オプションの両方を指定した場合、後に指定したほうを優先します。どちらも指定していない場合は、+-L+ を指定したものとみなします。 [[operands]] == オペランド {{ディレクトリ}}:: 新しい作業ディレクトリのパス名です。絶対パスまたは元の作業ディレクトリからの相対パスで指定します。 + この値がハイフン一つ (`-') の場合、{zwsp}link:params.html#sv-oldpwd[+OLDPWD+ 変数]の値が指定されたものとみなします。このオペランドが与えられていない場合、{zwsp}link:params.html#sv-home[+HOME+ 変数]の値が指定されたものとみなします (+--default-directory+ オプションを指定した場合を除く)。 [[exitstatus]] == 終了ステータス 作業ディレクトリを正しく変更できた場合、終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 Cd コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX には +YASH_AFTER_CD+ 変数および +--default-directory=...+ オプションに関する規定はありません。POSIX は{{ディレクトリ}}としてハイフン一つを指定したときに +-L+ または +-P+ オプションを併用することを認めていません。 +YASH_AFTER_CD+ 変数の実行結果は cd コマンドの終了ステータスには影響しません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_colon.txt000066400000000000000000000017641354143602500156000ustar00rootroot00000000000000= コロン組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - コロン組込みコマンド dfn:[コロン組込みコマンド]は何も行わない組込みコマンドです。 [[syntax]] == 構文 - +: [{{引数}}...]+ [[description]] == 説明 コロンコマンドは何も行いません。コマンドライン引数は一切無視します。 [[exitstatus]] == 終了ステータス コロンコマンドの終了ステータスは 0 です。 [[notes]] == 補足 コロンコマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 引数の{zwsp}link:expand.html[展開]と{zwsp}link:redir.html[リダイレクト]は他のコマンドと同様に行います。 link:_true.html[True コマンド]はコロンコマンドと同様に何も行いませんが、コロンコマンドは特殊組込みコマンドであるのに対し true コマンドは準特殊組込みコマンドです。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_command.txt000066400000000000000000000127251354143602500161030ustar00rootroot00000000000000= Command 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Command 組込みコマンド dfn:[Command 組込みコマンド]はコマンドを実行します。またコマンドの種類を特定します。 [[syntax]] == 構文 - +command [-befp] {{コマンド}} [{{引数}}...]+ - +command -v|-V [-abefkp] {{コマンド}}...+ [[description]] == 説明 +-v+ (+--identify+) オプションならびに +-V+ (+--verbose-identify+) オプションを付けずに実行すると、command コマンドは与えられた{{コマンド}}を与えられた{{引数}}で実行します。コマンドの実行の仕方は{zwsp}link:exec.html#simple[単純コマンドの実行]の最後のステップに準じますが、{zwsp}link:exec.html#search[コマンドの検索]では外部コマンド・組込みコマンド・関数の内オプションで指定したものしか検索しません。またコマンドが{zwsp}link:builtin.html#types[特殊組込みコマンド]の場合、コマンドのオプションやオペランドの使い方が間違っていたりリダイレクトエラーや代入エラーが起きたりしてもシェルは終了しません。 +-v+ (+--identify+) オプションまたは +-V+ (+--verbose-identify+) オプションを付けて実行すると、command コマンドは与えられた{{コマンド}}の種類とパスを特定しそれを標準出力に出力します。{{コマンド}}は{zwsp}link:syntax.html#aliases[エイリアス]や{zwsp}link:exec.html#function[関数]であっても構いません。 +-v+ (+--identify+) オプションを付けて実行したときの出力は以下のようになります。 - link:exec.html#search[コマンドの検索]の結果見つかったコマンドおよびその他の外部コマンドは、その絶対パスを出力します。 - コマンドの検索によらず実行される組込みコマンドや関数は、単にその名前を出力します。 - link:syntax.html#tokens[予約語]は、単にその名前を出力します。 - エイリアスは、コマンドとして実行可能な形式でその名前と値を出力します。 - コマンドが見つからなかった場合は、何も出力しません。(終了ステータスが非 0 になります) +-V+ (+--verbose-identify+) オプション使用時は、出力の形式が人間にとってより読みやすくなります。 [[options]] == オプション +-a+:: +--alias+:: {{コマンド}}として{zwsp}link:syntax.html#aliases[エイリアス]を検索の対象にします。 +-v+ (+--identify+) または +-V+ (+--verbose-identify+) オプションと一緒に使う必要があります。 +-b+:: +--builtin-command+:: {{コマンド}}として組込みコマンドを検索の対象にします。 +-e+:: +--external-command+:: {{コマンド}}として外部コマンドを検索の対象にします。 +-f+:: +--function+:: {{コマンド}}として関数を検索の対象にします。 +-k+:: +--keyword+:: {{コマンド}}として予約語を検索の対象にします。 +-v+ (+--identify+) または +-V+ (+--verbose-identify+) オプションと一緒に使う必要があります。 +-p+:: +--standard-path+:: link:exec.html#search[コマンドの検索]において、{zwsp}link:params.html#sv-path[+PATH+ 変数]の代わりに、標準のコマンドをすべて含むようなシステム固有のデフォルトパスを用いて外部コマンドを検索します。 +-v+:: +--identify+:: 与えられた{{コマンド}}の種類とパスを特定し、簡単な形式で標準出力に出力します。 +-V+:: +--verbose-identify+:: 与えられた{{コマンド}}の種類とパスを特定し、人間にとって読みやすい形式で標準出力に出力します。 +-a+ (+--alias+), +-b+ (+--builtin-command+), +-e+ (+--external-command+), +-f+ (+--function+), +-k+ (+--keyword+) オプションのどれも指定しなかった場合は、以下のオプションを指定したものとみなします。 +-v+ (+--identify+) あるいは +-V+ (+--verbose-identify+) オプションを指定していないとき:: +-b -e+ +-v+ (+--identify+) または +-V+ (+--verbose-identify+) オプションを指定しているとき:: +-a -b -e -f -k+ [[operands]] == オペランド {{コマンド}}:: 実行するまたは種類を特定するコマンドの名前です。 {{引数}}...:: 実行するコマンドに渡すコマンドライン引数です。 [[exitstatus]] == 終了ステータス +-v+ (+--identify+) あるいは +-V+ (+--verbose-identify+) オプションを指定していないとき:: 実行したコマンドの終了ステータス +-v+ (+--identify+) または +-V+ (+--verbose-identify+) オプションを指定しているとき:: エラーがない限り 0 [[notes]] == 補足 Command コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX に規定のあるオプションは +-p+, +-v+, +-V+ だけです。これ以外のオプションは link:posix.html[POSIX 準拠モード]では使えません。また POSIX 準拠モードでは +-v+ または +-V+ オプションを使用するとき{{コマンド}}はちょうど一つしか指定できません。 POSIX は +-v+ オプションと +-V+ オプションを同時に指定することを認めていません。Yash ではこれら二つのオプションを両方指定すると最後に指定したものが有効になります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_complete.txt000066400000000000000000000150541354143602500162730ustar00rootroot00000000000000= Complete 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Complete 組込みコマンド dfn:[Complete 組込みコマンド]は{zwsp}link:lineedit.html#completion[コマンドライン補完]において補完候補を生成します。この組込みコマンドは補完関数の実行中にだけ使えます。 [[syntax]] == 構文 - +complete [-A {{パターン}}] [-R {{パターン}}] [-T] [-P {{接頭辞}}] [-S {{接尾辞}}] [-abcdfghjkuv] [[-O] [-D {{説明}}] {{単語}}...]+ [[description]] == 説明 補完関数の中でこの組込みコマンドを実行すると、complete コマンドは指定した引数に従って補完候補を生成します。どのオプション・オペランドで候補を生成するにせよ、実際に生成される候補は現在補完しようとしている (コマンドライン上に途中まで入力された) 単語に一致するものに限られます。 [[options]] == オプション +-A {{パターン}}+:: +--accept={{パターン}}+:: このオプションを指定すると、指定した{{link:pattern.html[パターン]}}にマッチする候補だけを生成します。このオプションは複数回指定できます (指定した全ての{{パターン}}にマッチする候補だけを生成します)。 +-D {{説明}}+:: +--description={{説明}}+:: このオプションを指定すると、このオプションで指定した{{説明}}が補完の際に候補の説明として表示されます。 +-O+:: +--option+:: 生成する候補をコマンドのオプションとみなすようにします。候補を画面上に一覧表示する際に自動的に先頭にハイフンを付加します。 +-P {{接頭辞}}+:: +--prefix={{接頭辞}}+:: このオプションで指定する{{接頭辞}}は現在補完しようとしている単語の接頭辞になっていなければなりません。このオプションを指定すると、候補生成の際にこのオプションで指定した{{接頭辞}}を無視してマッチングを行います。例えば補完しようとしている単語が +file:///home/user/docume+ であり、この URL をファイル名として補完したいとしましょう。この場合は、+complete -P file:// -f+ とすると URL から +file://+ を除いた残りの +/home/user/docume+ の部分に対してファイル名としての補完候補が生成されます。 +-R {{パターン}}+:: +--reject={{パターン}}+:: このオプションを指定すると、指定した{{link:pattern.html[パターン]}}にマッチする候補を生成しません。このオプションは複数回指定できます (指定した{{パターン}}の少なくとも一つにマッチする候補を全て除外します)。 +-S {{接尾辞}}+:: +--suffix={{接尾辞}}+:: 生成した各候補の末尾に{{接尾辞}}を付加します。 +-T+:: +--no-termination+:: 通常は、補完が終わった後に次の単語をすぐ入力できるように、補完した単語の直後に空白を自動的に挿入しますが、このオプションを指定したときは空白を挿入しません。 === 補完方式設定のためのオプション +-a+:: +--alias+:: link:syntax.html#aliases[エイリアス] (+--normal-alias --global-alias+ に同じ) +--array-variable+:: link:params.html#arrays[配列] +--bindkey+:: link:_bindkey.html[Bindkey コマンド]で利用可能な{zwsp}link:lineedit.html#commands[行編集コマンド] +-b+:: +--builtin-command+:: link:builtin.html[組込みコマンド] (+--special-builtin --semi-special-builtin --regular-builtin+ に同じ) +-c+:: +--command+:: コマンド (+--builtin-command --external-command --function+ に同じ) +-d+:: +--directory+:: ディレクトリ +--dirstack-index+:: ディレクトリスタックのインデックス +--executable-file+:: 実行可能ファイル +--external-command+:: 外部コマンド +-f+:: +--file+:: ファイル (ディレクトリ含む) +--finished-job+:: 終了したジョブの{zwsp}link:job.html#jobid[ジョブ ID] +--function+:: link:exec.html#function[関数] +--global-alias+:: グローバル{zwsp}link:syntax.html#aliases[エイリアス] +-g+:: +--group+:: (ファイルのパーミッションなどにおける) グループ +-h+:: +--hostname+:: ホスト名 +-j+:: +--job+:: link:job.html#jobid[ジョブ ID] +-k+:: +--keyword+:: シェルの{zwsp}link:syntax.html#tokens[予約語] +--normal-alias+:: 通常の (グローバルでない) link:syntax.html#aliases[エイリアス] +--regular-builtin+:: 通常の{zwsp}link:builtin.html[組込みコマンド] +--running-job+:: 実行中のジョブの{zwsp}link:job.html#jobid[ジョブ ID] +--scalar-variable+:: (配列を除いた通常の) link:params.html#variables[変数] +--semi-special-builtin+:: 準特殊{zwsp}link:builtin.html[組込みコマンド] +--signal+:: シグナル +--special-builtin+:: 特殊{zwsp}link:builtin.html[組込みコマンド] +--stopped-job+:: 停止中のジョブの{zwsp}link:job.html#jobid[ジョブ ID] +-u+:: +--username+:: ユーザのログイン名 +-v+:: +--variable+:: link:params.html#variables[変数] +-d+ (+--directory+) オプションを指定せずに +-f+ (+--file+) オプションを指定した場合、+-S …+ (+--suffix=…+) オプションの指定の有無にかかわらず、ディレクトリ名を表す補完候補には接尾辞としてスラッシュが付き、候補の直後には空白が入りません (+-S / -T+ を指定したときと同じ動作)。 link:job.html#jobid[ジョブ ID] の補完は先頭の +%+ を除いた部分に対して行われるので、補完しようとしている単語が既に +%+ を含んでいる場合は +%+ を接頭辞として指定してください。 [[operands]] == オペランド Complete コマンドのオペランドは、各オペランドがそれぞれ補完候補として扱われます。指定したオペランドのうち、現在補完しようとしている単語に合うものが補完候補となります。 [[exitstatus]] == 終了ステータス 候補が少なくとも一つ生成できた場合は、終了ステータスは 0 です。新たな候補が一つも生成できなかったときは、終了ステータスは 1 です。その他のエラーの場合は 2 以上の終了ステータスになります。 [[notes]] == 補足 Complete コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では complete コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_continue.txt000066400000000000000000000057531354143602500163140ustar00rootroot00000000000000= Continue 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Continue 組込みコマンド dfn:[Continue 組込みコマンド]は実行中のループの次の繰り返しに処理を移します。 [[syntax]] == 構文 - +continue [{{深さ}}]+ - +continue -i+ [[description]] == 説明 +-i+ (+--iteration+) オプションを付けずに実行すると、continue コマンドは現在実行中の link:syntax.html#for[for ループ]または link:syntax.html#while-until[while ループ]または link:syntax.html#while-until[until ループ]の繰り返しを中断し、直ちに次の繰り返しを開始します (while/until ループについては、ループの実行条件の判定からやり直します)。多重ループの中で実行した場合、内側から数えて{{深さ}}番目のループに対してこの動作を行います。{{深さ}}が指定されていないときは、最も内側のループに対してこの動作を行います ({{深さ}} = 1)。指定された{{深さ}}が実際に実行している多重ループの深さより大きい場合は最も外側のループに対してこの動作を行います。 +-i+ (+--iteration+) オプションを付けて実行すると、continue コマンドは現在実行中の{zwsp}link:_eval.html#iter[反復実行]の現在のコマンドの実行を中断し、直ちに次のコマンドの実行を開始します。 [[options]] == オプション +-i+:: +--iteration+:: ループではなく反復実行に対して作用します。 [[operands]] == オペランド {{深さ}}:: 内側から何番目のループに作用するのかを指定する 1 以上の自然数です。 [[exitstatus]] == 終了ステータス +-i+ (+--iteration+) オプションが指定されていないとき、continue コマンドの処理が成功すると終了ステータスは 0 です。+-i+ (+--iteration+) オプションが指定されているとき、continue コマンドの処理が成功すると continue コマンドの直前に実行されたコマンドの終了ステータスが continue コマンドの終了ステータスになります。 [[notes]] == 補足 Continue コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX には +-i+ (+--interact+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 POSIX では、continue コマンドを構文的に取り囲んでいないループの扱いを規定していません。例えば以下のような場合が該当します: - ループの中で{zwsp}link:exec.html#function[関数]が実行され、その中で continue コマンドが実行される - ループの実行中に{zwsp}link:_trap.html[トラップ]が実行され、その中で continue コマンドが実行される Yash では、このようなループは continue の対象に出来ません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_dirs.txt000066400000000000000000000063761354143602500154330ustar00rootroot00000000000000= Dirs 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Dirs 組込みコマンド dfn:[Dirs 組込みコマンド]はディレクトリスタックの内容を表示します。 [[syntax]] == 構文 - +dirs [-cv] [{{インデックス}}..]+ [[description]] == 説明 dfn:[ディレクトリスタック]とは、作業ディレクトリの変更の履歴をとる仕組みです。{zwsp}link:_pushd.html[Pushd コマンド]で作業ディレクトリを変更すると、元の作業ディレクトリがディレクトリスタックに追加されます。{zwsp}link:_popd.html[Popd コマンド]を使うと、ディレクトリスタックに保存してある元の作業ディレクトリに戻ることができます。Dirs コマンドを使うと、ディレクトリスタックの内容を表示することができます。ディレクトリスタックの内容は link:params.html#sv-dirstack[+DIRSTACK+ 配列]と link:params.html#sv-pwd[+PWD+ 変数]に保存されます。これらの値を変更すると、ディレクトリスタックの動作に影響します。 ディレクトリスタックに保存してあるディレクトリはdfn:[インデックス]で区別します。インデックスは +-v+ (+--verbose+) オプションを付けて dirs コマンドを実行することで知ることができます。インデックスは正号 (+++) または負号 (+-+) の付いた整数の形で表わします。整数は pushd コマンドでディレクトリスタックに追加した順に振られます。例えばインデックス ++0+ は現在の作業ディレクトリに対応します。インデックス ++1+ は最後に追加したディレクトリで、インデックス ++2+ はその一つ前に追加したディレクトリに対応します。インデックス +-0+ は最初に 追加したディレクトリ、インデックス +-1+ はその次に追加したディレクトリに対応します。 +-c+ (+--clear+) オプションを付けずに実行すると、dirs コマンドは現在のディレクトリスタックの要素を一つずつ標準出力に出力します。 +-c+ (+--clear+) オプションを付けて実行すると、dirs コマンドはディレクトリスタックのインデックス ++0+ 以外の要素をすべて削除します。 [[options]] == オプション +-c+:: +--clear+:: ディレクトリスタックの要素を (現在の作業ディレクトリに対応するものを除いて) すべて削除します。 +-v+:: +--verbose+:: ディレクトリスタックの要素のインデックスも出力します。 [[operands]] == オペランド {{インデックス}}:: 表示するディレクトリスタックの要素のインデックスです。インデックスを一つも指定しないときは、全ての要素をインデックス ++0+ のものから順に表示します。 [[exitstatus]] == 終了ステータス エラーがない限り dirs コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Dirs コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では dirs コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_disown.txt000066400000000000000000000024141354143602500157620ustar00rootroot00000000000000= Disown 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Disown 組込みコマンド dfn:[Disown 組込みコマンド]はジョブを削除します。 [[syntax]] == 構文 - +disown [-a] [{{ジョブ}}...}+ [[description]] == 説明 Disown コマンドはシェルが管理しているジョブを削除します。削除したジョブは{zwsp}link:job.html[ジョブ制御]の対象から外れますが、ジョブを構成するコマンドの実行は継続します。 [[options]] == オプション +-a+:: +--all+:: 全てのジョブを削除します。 [[operands]] == オペランド {{ジョブ}}:: 削除するジョブの{zwsp}link:job.html#jobid[ジョブ ID]。 + 複数指定することもできます。何も指定しないと現在のジョブを削除します。非 link:posix.html[POSIX 準拠モード]ではジョブ ID の先頭の +%+ は省略できます。 [[exitstatus]] == 終了ステータス エラーがない限り disown コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Disown コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では disown コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_dot.txt000066400000000000000000000071531354143602500152520ustar00rootroot00000000000000= ドット組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - ドット組込みコマンド dfn:[ドット組込みコマンド]はテキストファイルを開いて、その内容をコマンドとして解釈し実行します。 [[syntax]] == 構文 - +. [-AL] {{ファイル名}} [{{引数}}...]+ [[description]] == 説明 ドットコマンドは与えられた{{ファイル名}}のファイルを開き、その内容をコマンドとして解釈し{zwsp}link:exec.html#environment[現在のコマンド実行環境]で実行します。 {{ファイル名}}に続けて{{引数}}が与えられているときは、{zwsp}link:exec.html#function[関数]の実行の時と同様に、コマンドの実行前に{{引数}}が{zwsp}link:params.html#positional[位置パラメータ]に設定され、実行後に元の位置パラメータに戻ります。 {{ファイル名}}にスラッシュ (+/+) が一つも入っていない場合は、{zwsp}link:exec.html#search[コマンドの検索]のときと同様に link:params.html#sv-path[+PATH+ 変数]の検索を行い、開くべきファイルを探します。ただしファイルは読み込み可能でさえあれば実行可能である必要はありません。検索の結果ファイルが見つかれば、そのファイルの内容を解釈・実行します。ファイルが見つからなかった場合、link:posix.html[POSIX 準拠モード]では直ちにエラーになります。POSIX 準拠モードでないときは現在の作業ディレクトリのファイルを開くことを試みます。 [[options]] == オプション +-A+:: +--no-alias+:: ファイルを読み込んで実行する際、エイリアス展開を行いません。 +-L+:: +--autoload+:: {{ファイル名}}がスラッシュを含んでいるかどうかにかかわらず、+PATH+ 変数の代わりに link:params.html#sv-yash_loadpath[+YASH_LOADPATH+ 変数]を検索して開くべきファイルを探します。{{ファイル名}}は現在の作業ディレクトリからの相対パス名とはみなしません。 ドットコマンドでは、最初のオペランドより後にあるコマンドライン引数は全てオペランドとして解釈します。 [[operands]] == オペランド {{ファイル名}}:: 読み込むファイルのパス名です。 {{引数}}...:: ファイルの内容を実行している間に位置パラメータに設定する文字列です。 [[exitstatus]] == 終了ステータス ドットコマンドの終了ステータスは、ファイルから読み込んで実行した最後のコマンドの終了ステータスです。ファイルの内容に一つもコマンドが入っていなかったときは終了ステータスは 0 です。ファイルが見つからなかったり開けなかったりしたときは終了ステータスは非 0 です。 [[notes]] == 補足 ドットコマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 シェルが{zwsp}link:interact.html[対話モード]でないとき、読み込むべきファイルが見つからなかったり開けなかったりするとシェルは直ちに終了します。 POSIX にはオプションに関する規定はありません。よってオプションは link:posix.html[POSIX 準拠モード]では使えません。 POSIX には{{引数}}オペランドによって位置パラメータを変更できることについての規定はありません。よって POSIX 準拠モードでは{{引数}}オペランドを与えるとエラーになります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_echo.txt000066400000000000000000000103361354143602500153770ustar00rootroot00000000000000= Echo 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Echo 組込みコマンド dfn:[Echo 組込みコマンド]はコマンドライン引数を標準出力に出力します。 [[syntax]] == 構文 - +echo [{{文字列}}...]+ Echo コマンドはコマンドライン引数を全てオペランドとして解釈します。オプションは、以下に述べる例外を除いて一切解釈しません。オプションはオペランドより前に置く必要があります。Echo コマンドでは構文エラーは絶対に起きません。 [[description]] == 説明 Echo コマンドは与えられたコマンドライン引数と改行を標準出力に出力します。引数がない場合は改行だけを出力します。引数が複数ある場合はそれぞれを空白文字で区切って出力します。 [[escapes]] === エスケープシーケンス Echo コマンドに与える引数では、後述の +ECHO_STYLE+ 変数と +-e+ オプションの指定によって以下のエスケープシーケンスを使用することができます。 +\a+:: ベル文字 (ASCII コード番号 7) +\b+:: バックスペース (ASCII コード番号 8) +\c+:: これ以降何も出力しない。 +\e+:: エスケープ文字 (ASCII コード番号 27) +\f+:: フォームフィード (ASCII コード番号 12) +\n+:: 改行文字 (ASCII コード番号 10) +\r+:: 復帰文字 (ASCII コード番号 13) +\t+:: 水平タブ (ASCII コード番号 9) +\v+:: 垂直タブ (ASCII コード番号 11) +\\+:: バックスラッシュ +\0{{xxx}}+:: 八進数 {{xxx}} (最大三桁) で表わされるコード番号の文字 エスケープシーケンスが無効の時は、エスケープシーケンスは解釈せずにそのまま出力します。 [[echo_style]] === +ECHO_STYLE+ 変数 Echo コマンドがオプションやエスケープシーケンスを解釈するかどうかは link:params.html#sv-echo_style[+ECHO_STYLE+ 変数]の値によります。以下に、この変数の値と echo コマンドの動作との対応を示します。 +SYSV+:: +XSI+:: オプションは一切解釈しません。常にエスケープシーケンスを解釈します。 +BSD+:: +-n+ オプションを解釈します。エスケープシーケンスは一切解釈しません。 +GNU+:: +-n+, +-e+, +-E+ オプションを解釈します。エスケープシーケンスは +-e+ オプションを指定したときだけ解釈します。 +ZSH+:: +-n+, +-e+, +-E+ オプションを解釈します。エスケープシーケンスは +-E+ オプションを指定しないかぎり解釈します。 +DASH+:: +-n+ オプションを解釈します。常にエスケープシーケンスを解釈します。 +RAW+:: オプションもエスケープシーケンスも一切解釈しません。 +ECHO_STYLE+ 変数が設定されていないときは、値が +SYSV+ または +XSI+ の場合の動作をします。 [[options]] == オプション +-n+:: 最後に改行を出力しないようにする。 +-e+:: エスケープシーケンスを解釈するようにする。 +-E+:: エスケープシーケンスを解釈せず、全ての文字をそのまま出力するようにする。 [[exitstatus]] == 終了ステータス エラーがない限り echo コマンドの終了ステータスは 0 です。 [[notes]] == 補足 POSIX には +ECHO_STYLE+ 変数およびオプションに関する規定はありません。POSIX では、+-n+ オプションが指定されたときまたは引数にバックスラッシュが含まれている場合の動作を規定していません。可搬性のあるシェルスクリプトを書くには、echo コマンドよりも link:_printf.html[printf コマンド]の使用を推奨します。 +ECHO_STYLE+ 変数のとれる値は主に他のシェルの実装を基に決めてありますが、これらの実装を yash が完全に模倣することを意図するものではありません。Zsh の echo 組込みには単一のハイフンをオプションとオペランドの区切りとして解釈するという挙動がありますが、yash ではこのようなハイフンの使い方はできません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_eval.txt000066400000000000000000000056651354143602500154210ustar00rootroot00000000000000= Eval 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Eval 組込みコマンド dfn:[Eval 組込みコマンド]はオペランドをコマンドとして解釈して実行します。 [[syntax]] == 構文 - +eval [-i] [{{コマンド}}...]+ Eval コマンドでは、{zwsp}link:posix.html[POSIX 準拠モード]であるかどうかにかかわらずオプションはオペランドより先に全て指定しなければなりません。最初のオペランドより後にあるコマンドライン引数は全てオペランドとして解釈します。 [[description]] == 説明 Eval コマンドは、与えられたオペランドをシェルのコマンドとして解釈し、{zwsp}link:exec.html#environment[現在のコマンド実行環境]で実行します。 +-i+ (+--iteration+) オプションが指定されていないときは、コマンドをまとめて一度に解釈・実行します。複数のオペランドがある場合は、それらを順に連結して一つにしてから解釈・実行します (連結の際、各オペランド間に空白文字を一つずつ区切りとして挿入します)。 +-i+ (+--iteration+) オプションが指定されているときは、オペランドを順に一つずつ解釈・実行します。これをdfn:iter[反復実行]といいます。反復実行の途中で link:_continue.html[continue コマンド]を +-i+ オプション付きで実行した場合、コマンドの実行は中断され、eval コマンドに与えられた次のオペランドの解釈・実行に移ります。反復実行の途中で link:_break.html[break コマンド]を +-i+ オプション付きで実行した場合、反復実行は中断され、この eval コマンドの実行は終了します。{zwsp}link:params.html#special[特殊パラメータ +?+] の値が反復実行の開始前に保存されます。オペランドを一つ解釈・実行するたびに特殊パラメータ +?+ の値は保存された値にもどります。 [[options]] == オプション +-i+:: +--iteration+:: 与えられたコマンドを順に反復実行します。 [[operands]] == オペランド {{コマンド}}:: コマンドとして解釈・実行する文字列です。 [[exitstatus]] == 終了ステータス オペランドが一つもない場合またはオペランドの中にコマンドが一つも含まれていなかった場合、終了ステータスは 0 です。コマンドが一つ以上解釈・実行された場合、最後に実行したコマンドの終了ステータスが eval コマンドの終了ステータスになります。 [[notes]] == 補足 Eval コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX にはオプションに関する規定はありません。よってオプションは link:posix.html[POSIX 準拠モード]では使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_exec.txt000066400000000000000000000077721354143602500154170ustar00rootroot00000000000000= Exec 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Exec 組込みコマンド dfn:[Exec 組込みコマンド]はシェルのプロセスを別の外部コマンドに置き換えます。またシェルのプロセスに対してリダイレクトを実行します。 [[syntax]] == 構文 - +exec [-cf] [-a {{コマンド名}}] [{{コマンド}} [{{引数}}...]]+ Exec コマンドでは、{zwsp}link:posix.html[POSIX 準拠モード]であるかどうかにかかわらずオプションはオペランドより先に全て指定しなければなりません。これは exec コマンドに対するオプションと{{コマンド}}に対するオプションを区別するために重要です。{{コマンド}}より後にある引数はすべて{{引数}}とみなされます。 [[description]] == 説明 Exec コマンドを{{コマンド}}を指定して実行すると、シェルは{zwsp}link:exec.html#simple[単純コマンドの実行]の最後のステップと同様にしてコマンドを実行します。ただし、コマンドは必ず外部コマンドとしてみなされ、関数や組込みコマンドは無視します。そしてその外部コマンドはサブシェルではなく現在の{zwsp}link:exec.html#environment[コマンド実行環境]で exec システムコールを呼び出すことで実行します。これにより、シェルのプロセスは新しく起動するコマンドに置き換わります。 シェルが link:posix.html[POSIX 準拠モード]のときまたは{zwsp}link:interact.html[対話モード]でないとき、コマンドの起動に失敗するとシェルは直ちに終了します。 シェルが POSIX 準拠モードではなくかつ対話モードのとき、停止中のジョブがあると、シェルは警告を表示し、コマンドを起動しません。一度 exec が実行されると、シェルが持っているジョブの情報は失われるため、手動でシグナルを送ってジョブを再開または終了させなければならなくなります。警告を無視してコマンドを起動するには +-f+ (+--force+) オプションを付けてください。 {{コマンド}}なしで実行した場合 exec コマンドは何も行いませんが、この exec コマンドを実行する際に行った{zwsp}link:redir.html[リダイレクト]の効果は現在のコマンド実行環境に残ります。 [[options]] == オプション +-a {{コマンド名}}+:: +--as={{コマンド名}}+:: {{コマンド}}の代わりに{{コマンド名}}をコマンド名としてコマンドに渡します。 +-c+:: +--clear+:: 既存の{zwsp}link:params.html#variables[環境変数]をすべて削除した状態でコマンドを実行します。ただしこの exec コマンドを実行する際に行った変数代入の結果は環境変数としてコマンドに渡します。 +-f+:: +--force+:: 警告を無視してコマンドを実行します。 [[operands]] == オペランド {{コマンド}}:: 実行するコマンドです。 {{引数}}...:: 実行するコマンドに渡すコマンドライン引数です。 [[exitstatus]] == 終了ステータス 指定されたコマンドの起動に成功した場合、シェルのプロセスはそのコマンドのプロセスに置き換わってしまうので、終了ステータスはありません。 実行しようとしたコマンドが見つからなかった場合、終了ステータスは 127 です。コマンドが見つかったが実行できなかった場合、終了ステータスは 126 です。{{コマンド}}を指定せずに exec コマンドを実行した場合、終了ステータスは 0 です。 [[notes]] == 補足 Exec コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX にはオプションに関する規定はありません。よってオプションは link:posix.html[POSIX 準拠モード]では使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_exit.txt000066400000000000000000000057051354143602500154360ustar00rootroot00000000000000= Exit 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Exit 組込みコマンド dfn:[Exit 組込みコマンド]コマンドはシェルの実行を終了します。 [[syntax]] == 構文 - +exit [-f] [{{終了ステータス}}]+ [[description]] == 説明 Exit コマンドは、このコマンドを実行したシェル (または{zwsp}link:exec.html#subshell[サブシェル]) を終了します。 停止している{zwsp}link:job.html[ジョブ]のある{zwsp}link:interact.html[対話モード]のシェルを終了しようとすると、シェルは警告を表示し、終了しません。+-f+ (+--force+) オプションを付けて実行するか exit コマンドを二連続で実行すると警告を無視してシェルを終了します。 シェル終了時の{zwsp}link:_trap.html[トラップ]が設定されている場合は、シェルが終了する前にそれが実行されます。 [[options]] == オプション +-f+:: +--force+:: 警告を無視してシェルを終了します。 [[operands]] == オペランド {{終了ステータス}}:: 終了するシェルの終了ステータスを指定する 0 以上の自然数です。 + このオペランドが与えられていない場合は、exit コマンドの直前に実行されたコマンドの終了ステータスを用います (ただし{zwsp}link:_trap.html[トラップ]を実行中の場合はトラップに入る直前のコマンドの終了ステータス)。 + 終了するシェルの実際の終了ステータスは、オペランドで与えられた数を 256 で割った余りになります。 [[exitstatus]] == 終了ステータス Exit コマンドはシェルを終了するので、exit コマンドそのものの終了ステータスはありません。 例外として、exit コマンドが警告を表示して、シェルを終了しなかった場合、exit コマンドの終了ステータスは非 0 です。 [[notes]] == 補足 Exit コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX では、{{終了ステータス}}の値は 0 以上 256 未満でなければならないとしています。Yash では拡張として 256 以上の値も受け付けるようになっています。 POSIX には +-f+ (+--force+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 シェル終了時のトラップの実行中に exit コマンドを実行すると、再びトラップが実行されることはなくそのままシェルは終了します。このとき exit コマンドに{{終了ステータス}}が与えられていない場合は、もし終了時のトラップが設定されていなかった場合にシェルが返したろう終了ステータスでシェルは終了します。(link:exec.html#exit[シェルの終了]も参照) // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_export.txt000066400000000000000000000020531354143602500157770ustar00rootroot00000000000000= Export 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Export 組込みコマンド dfn:[Export 組込みコマンド]はエクスポート対象の{zwsp}link:params.html#variables[変数]を表示・設定します。 [[syntax]] == 構文 - +export [-prX] [{{変数}}[={{値}}]...]+ [[description]] == 説明 Export コマンドは link:_typeset.html[typeset コマンド]に +-gx+ オプションを付けたものと同じです。その他オプション・オペランド・終了ステータスは typeset コマンドと同様です。 [[notes]] == 補足 Export コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX には export コマンドに関する規定はありますが、オプションは +-p+ しか規定がありません。その他のオプションは link:posix.html[POSIX 準拠モード]では使えません。また POSIX は +-p+ オプションをオペランドとともに使うことを認めていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_false.txt000066400000000000000000000012051354143602500155460ustar00rootroot00000000000000= False 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - False 組込みコマンド dfn:[False 組込みコマンド]は何も行わずに非 0 の終了ステータスで終了します。 [[syntax]] == 構文 - +false+ [[description]] == 説明 False コマンドは何も行いません。コマンドライン引数は一切無視します。 [[exitstatus]] == 終了ステータス False コマンドの終了ステータスは非 0 です。 [[notes]] == 補足 False コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_fc.txt000066400000000000000000000114031354143602500150450ustar00rootroot00000000000000= Fc 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Fc 組込みコマンド dfn:[Fc 組込みコマンド]は{zwsp}link:interact.html#history[コマンド履歴]に記録されたコマンドを再実行・表示します。 [[syntax]] == 構文 - +fc [-qr] [-e {{エディタ}}] [{{始点}} [{{終点}}]]+ - +fc -s[q] [{{前}}={{後}}] [{{始点}}]+ - +fc -l[nrv] [{{始点}} [{{終点}}]]+ [[description]] == 説明 +-l+ (+--list+) オプションを付けずに実行すると、fc コマンドはオペランドで指定した{zwsp}link:interact.html#history[コマンド履歴]のコマンドを再実行します。+-s+ (+--silent+) オプションを付けていない場合、シェルはコマンドを再実行する前にエディタを起動し、コマンドを編集できるようにします。エディタが終了するとシェルは編集後のコマンドを実行します。+-s+ (+--silent+) オプションを付けた場合、シェルはエディタを起動せず直接コマンドを再実行します。いずれの場合も、実行するコマンドは標準出力に出力しコマンド履歴に追加されます。 +-l+ (+--list+) オプションを付けて実行すると、fc コマンドはオペランドで指定した範囲のコマンド履歴を標準出力に出力します。標準では履歴内のコマンドの内容を履歴番号とともに表示しますが、+-n+ (+--no-numbers+) および +-v+ (+--verbose+) オプションにより出力形式を変更できます。 [[options]] == オプション +-e {{エディタ}}+:: +--editor={{エディタ}}+:: コマンドの編集に用いるエディタ。 + このオプションを指定しない場合、{zwsp}link:params.html#sv-fcedit[+FCEDIT+ 変数]の値をエディタとして使用します。+FCEDIT+ 変数も設定されていない場合は、ed をエディタとして使用します。 +-l+:: +--list+:: コマンド履歴の内容を表示します。 +-n+:: +--no-numbers+:: コマンド履歴の内容を表示する際、履歴番号を省いてコマンドのみ表示します。 +-q+:: +--quiet+:: コマンドを実行する前にコマンドを出力しないようにします。 +-r+:: +--reverse+:: {{始点}}と{{終点}}を入れ替えます。 +-s+:: +--silent+:: コマンドを編集せずに直接再実行します。 +-v+:: +--verbose+:: コマンド履歴の内容を表示する際、コマンドの時刻も表示します。 [[operands]] == オペランド {{始点}}と{{終点}}:: {{始点}}と{{終点}}のオペランドは、再実行または表示するコマンドの範囲を指定します。{{始点}}あるいは{{終点}}に整数を指定すると、それは履歴番号とみなします。負の整数は最新の履歴から数えた番号となります。例えば +-2+ は最後から二番目に履歴に登録されたコマンドを表します。整数以外の文字列を{{始点}}あるいは{{終点}}に指定すると、その文字列で始まる最新の履歴を指定しているものとみなします。 + Fc コマンドが再実行または表示するコマンドは、{{始点}}と{{終点}}で指定したコマンドとその間にある履歴のコマンドです。{{始点}}が{{終点}}より後のコマンドを指している場合、コマンドの順序は逆になります。 + {{始点}}または{{終点}}が与えられていない場合のデフォルト値は以下の表のとおりです。 + [width="50%",options="header"] |=== | |+-l+ あり |+-l+ なし |{{始点}} |-16 |-1 |{{終点}} |-16 |{{始点}}に同じ |=== {{前}}={{後}}:: {{前}}={{後}}の形式のオペランドは、コマンドの一部を書き換えることを指示します。再実行するコマンドの中に{{前}}と同じ文字列がある場合は、その部分を{{後}}に置き換えて実行します。該当部分が複数ある場合は、最初のものだけを置き換えます。 [[exitstatus]] == 終了ステータス コマンドを正しく再実行できた場合、fc コマンドの終了ステータスは再実行したコマンドの終了ステータスになります。+-l+ (+--list+) オプションを指定した場合は、履歴が正しく出力できれば終了ステータスは 0 です。 [[notes]] == 補足 Fc コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX には +-q+ (+--quiet+) および +-v+ (+--verbose+) オプションに関する規定はありません。よってこれらのオプションは link:posix.html[POSIX 準拠モード]では使えません。 link:lineedit.html[行編集]の動作中は履歴の内容を変更することはできません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_fg.txt000066400000000000000000000034631354143602500150600ustar00rootroot00000000000000= Fg 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Fg 組込みコマンド dfn:[Fg 組込みコマンド]はジョブをフォアグラウンドで実行します。 [[syntax]] == 構文 - +fg [{{ジョブ}}...]+ [[description]] == 説明 Fg コマンドはジョブをフォアグラウンドに移動し SIGCONT シグナルを送ります。これにより、ジョブが停止していた場合はフォアグラウンドで実行が再開されます。Fg コマンドはジョブの実行が終了するまで待機し、ジョブの終了ステータスを返します。 ジョブの実行を再開する前に fg コマンドはジョブの名前を標準出力に出力します。 非 link:posix.html[POSIX 準拠モード]ではジョブの番号も出力されます。 Fg コマンドは{zwsp}link:job.html[ジョブ制御]が有効な時しか使えません。 [[operands]] == オペランド {{ジョブ}}:: 実行するジョブの{zwsp}link:job.html#jobid[ジョブ ID]。 + 複数指定すると指定した順に一つずつジョブをフォアグラウンドで実行します。何も指定しないと現在のジョブを実行します。 + 非 link:posix.html[POSIX 準拠モード]ではジョブ ID の先頭の +%+ は省略できます。 [[exitstatus]] == 終了ステータス ジョブを正しく実行できた場合、fg コマンドの終了ステータスは (最後に) 実行したジョブの終了ステータスです。エラーが発生した場合は終了ステータスは非 0 です。 [[notes]] == 補足 Fg コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 link:posix.html[POSIX 準拠モード]では{{ジョブ}}は一つまでしか指定できません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_getopts.txt000066400000000000000000000123451354143602500161500ustar00rootroot00000000000000= Getopts 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Getopts 組込みコマンド dfn:[Getopts 組込みコマンド]はコマンドライン引数のオプションを解析します。 [[syntax]] == 構文 - +getopts {{オプションリスト}} {{変数名}} [{{引数}}...]+ [[description]] == 説明 Getopts コマンドは、オペランドで与えられたコマンドライン引数に含まれている{zwsp}link:builtin.html#argsyntax[一文字のオプション]を解析します。Getopts コマンドを一回呼び出すたびにオプションが一つ解析され、そのオプションを表す一文字が{{変数名}}で指定した{zwsp}link:params.html#variables[変数]に代入されます。 解析の対象となるオプションの種類もオペランドで指定します。{{オプションリスト}}には受け付けるオプションの文字を並べて指定します。文字の後にコロン (+:+) を付けるとそのオプションは引数をとるものとみなします。例えば、+-a+, +-b+, +-c+ の三種類のオプションを受け付け、さらにこれらのうち +-b+ が引数をとる場合、{{オプションリスト}}には +ab:c+ を指定します。 引数をとるオプションを解析したとき、その引数の値が link:params.html#sv-optarg[+OPTARG+ 変数]に代入されます。 {{オプションリスト}}で与えられていないオプションに出くわしたときまたは引数をとるオプションに引数が与えられていないときの動作は、{{オプションリスト}}の最初の文字がコロン (+:+) であるかどうかで決まります。 - {{オプションリスト}}の最初の文字がコロンの場合、そのオプションの文字が +OPTARG+ 変数に代入され、{{変数名}}で指定した変数には +?+ ({{オプションリスト}}で与えられていないオプションに出くわしたとき) または +:+ (引数をとるオプションに引数が与えられていないとき) が代入されます。 - {{オプションリスト}}の最初の文字がコロンでない場合、+OPTARG+ 変数は削除され、{{変数名}}で指定した変数には +?+ が代入されます。またこのとき標準エラーにエラーメッセージが出力されますが、それでも getopts コマンドの終了ステータスは 0 になります。 Getopts コマンドは、実行するたびに一つずつオプションを解析します。全てのオプションを解析するには、毎回同じ{{引数}}で getopts コマンドを繰り返し実行する必要があります。シェルは、オプションをどこまで解析したかを覚えておくために、{zwsp}link:params.html#sv-optind[+OPTIND+ 変数]を用います。全てのオプションを解析し終わるまでにこの変数を変更してはいけません。全てのオプションを解析し終わると、+OPTIND+ 変数には{{引数}} (または位置パラメータ) の中で最初のオペランドに当たるもののインデックスが代入されます (オペランドがない場合は{{引数}}または位置パラメータの個数 + 1 になります)。 異なる{{引数}}を解析させたい場合は、getopts コマンドに新しい{{引数}}を与える前に +OPTIND+ 変数に +1+ を代入してください。 [[operands]] == オペランド {{オプションリスト}}:: 解析の対象となるオプションの文字の羅列です。 {{変数名}}:: 解析結果の値を代入する変数の名前です。 {{引数}}s:: 解析するコマンドライン引数です。 + このオペランドを指定しない場合は、{zwsp}link:params.html#positional[位置パラメータ]を解析します。 [[exitstatus]] == 終了ステータス {{引数}}の中にオプションが見つかった場合は、(それが{{オプションリスト}}に含まれているかどうかにかかわらず) 終了ステータスは 0 です。全てのオプションを解析し終わった時は、終了ステータスは非 0 です。 [[example]] == 使用例 ---- aopt=false bopt= copt=false while getopts ab:c opt do case $opt in a) aopt=true ;; b) bopt=$OPTARG ;; c) copt=true ;; \?) return 2 ;; esac done if $aopt; then echo オプション -a が指定されました; fi if [ -n "$bopt" ]; then echo オプション -b $bopt が指定されました; fi if $copt; then echo オプション -c が指定されました; fi shift $((OPTIND - 1)) echo オペランドは $* ---- [[notes]] == 補足 Getopts コマンドが解析するコマンドライン引数では、オプションは全てオペランドより前に指定してある必要があります。最初にオペランドが現れた時点で、getopts コマンドは解析を終了します。 Getopts コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX は、+OPTIND+ 変数に +1+ 以外の値を代入した場合の動作を規定していません。 link:posix.html[POSIX 準拠モード]では、{{オプションリスト}}に含まれるオプションは英数字でなければなりません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_hash.txt000066400000000000000000000061161354143602500154050ustar00rootroot00000000000000= Hash 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Hash 組込みコマンド dfn:[Hash 組込みコマンド]は外部コマンドのパスを検索・表示します。 [[syntax]] == 構文 - +hash {{コマンド}}...+ - +hash -r [{{コマンド}}...]+ - +hash [-a]+ - +hash -d {{ユーザ名}}...+ - +hash -dr [{{ユーザ名}}...]+ - +hash -d+ [[description]] == 説明 オプションを指定しない場合、hash コマンドはオペランドで指定した{zwsp}link:exec.html#search[外部コマンドのパスを検索]し、結果を記憶します (既に記憶している場合は再度検索・記憶します)。 +-r+ (+--remove+) オプションを指定している場合、hash コマンドはオペランドで指定した外部コマンドのパスに関する記憶を消去します。+-r+ (+--remove+) オプションを指定しかつ{{コマンド}}を指定しない場合、全ての記憶を消去します。 +-r+ (+--remove+) オプションを指定せず{{コマンド}}も指定しない場合、記憶しているパスの一覧を標準出力に出力します。 +-d+ (+--directory+) オプションを指定した場合、hash コマンドは外部コマンドのパスの代わりにユーザのホームディレクトリのパスを検索・記憶または表示します。記憶したパスは{zwsp}link:expand.html#tilde[チルダ展開]で使用します。 [[options]] == オプション +-a+:: +--all+:: シェルが記憶している全てのパスを出力します。 + このオプションを指定しない場合、シェルが記憶しているパスのうち組込みコマンドに対するものは出力しません。 +-d+:: +--directory+:: 外部コマンドのパスの代わりにユーザのホームディレクトリのパスを扱います。 +-r+:: +--remove+:: 指定したコマンドまたはユーザ名に対するパスの記憶を消去します。 [[operands]] == オペランド {{コマンド}}:: パスを記憶・消去する外部コマンドの名前です。スラッシュを含むパスを指定することはできません。 {{ユーザ名}}:: ホームディレクトリのパスを記憶・消去するユーザ名です。 [[exitstatus]] == 終了ステータス エラーがない限り hash コマンドの終了ステータスは 0 です。 [[notes]] == 補足 シェルは、外部コマンド (またはチルダ展開) を実行する際に自動的にコマンド (またはホームディレクトリ) のパスを記憶するので、通常はわざわざ hash コマンドを使ってパスを記憶させる必要はありません。 link:params.html#sv-path[+PATH+ 変数]の値が変わった時は、記憶した外部コマンドのパスは自動的にすべて消去されます。 POSIX が規定しているオプションは +-r+ だけです。よって他のオプションは link:posix.html[POSIX 準拠モード]では使えません。 Hash コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_help.txt000066400000000000000000000020511354143602500154040ustar00rootroot00000000000000= Help 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Help 組込みコマンド dfn:[Help 組込みコマンド]は組込みコマンドに関する説明を表示します。 [[syntax]] == 構文 - +help [{{コマンド}}...]+ [[description]] == 説明 Help 組込みコマンドは、オペランドで指定した組込みコマンドに関する説明を出力します。 [[operands]] == オペランド {{コマンド}}:: 説明を表示する組込みコマンドの名前です。 [[exitstatus]] == 終了ステータス エラーがない限り help コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Help コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では help コマンドの動作は規定されていません。 Yash の多くの組込みコマンドでは、+--help+ オプションを与えることで help コマンドの出力と同様の説明を表示させることができます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_history.txt000066400000000000000000000054351354143602500161660ustar00rootroot00000000000000= History 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - History 組込みコマンド dfn:[History 組込みコマンド]は{zwsp}link:interact.html#history[コマンド履歴]を編集します。 [[syntax]] == 構文 - +history [-cF] [-d {{項目}}] [-s {{コマンド}}] [-r {{ファイル}}] [-w {{ファイル}}] [{{個数}}]+ [[description]] == 説明 History コマンドは{zwsp}link:interact.html#history[コマンド履歴]の内容を編集・表示します。 オプションが指定してある場合、history コマンドはそのオプションに従ってコマンド履歴の内容を編集します。複数のオプションがある場合は指定した順に処理します。 オペランドで{{個数}}が与えられている場合、history コマンドはコマンド履歴の内容をその個数だけ標準出力に出力します。出力の形式は link:_fc.html[fc コマンド]に準じます。 オプションもオペランドも与えられていない場合、history コマンドはコマンド履歴の内容を全て標準出力に出力します。 [[options]] == オプション +-c+:: +--clear+:: コマンド履歴をすべて削除します。 +-d {{項目}}+:: +--delete={{項目}}+:: 指定した{{項目}}をコマンド履歴から削除します。{{項目}}の指定の仕方は link:_fc.html#operands[fc コマンドの{{始点}}・{{終点}}]オペランドと同じです。 +-F+:: +--flush-file+:: 履歴ファイルを再構築します。 +-r {{ファイル}}+:: +--read={{ファイル}}+:: 指定した{{ファイル}}からコマンドを読み込み履歴に追加します。ファイルの内容は単なるテキストファイルとして解釈され、それぞれの行の内容が一つのコマンドとして追加されます。 +-s {{コマンド}}+:: +--set={{コマンド}}+:: コマンド履歴の最後の項目を削除し、代わりに指定した{{コマンド}}を追加します。 +-w {{ファイル}}+:: +--write={{ファイル}}+:: 指定した{{ファイル}}に現在のコマンド履歴の内容を全て書き出します。既にあるファイルの内容は消去します。履歴は単なるテキストとして一行ずつ書き出します。 [[operands]] == オペランド {{個数}}:: 表示する履歴の個数です。 [[exitstatus]] == 終了ステータス エラーがない限り history コマンドの終了ステータスは 0 です。 [[notes]] == 補足 History コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では history コマンドの動作は規定されていません。 link:lineedit.html[行編集]の動作中は履歴の内容を変更することはできません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_jobs.txt000066400000000000000000000043421354143602500154160ustar00rootroot00000000000000= Jobs 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Jobs 組込みコマンド dfn:[Jobs 組込みコマンド]はシェルが有している{zwsp}link:job.html[ジョブ]を表示します。 [[syntax]] == 構文 - +jobs [-lnprs] [{{ジョブ}}...]+ [[description]] == 説明 Jobs コマンドはシェルが現在有している{zwsp}link:job.html[ジョブ]の名前や状態を表示します。 標準では各ジョブについて以下の情報を一行ずつ表示します。 - ジョブ番号 - 現在のジョブ・前のジョブを示す記号 (+++ または +-+) - 状態 - コマンド名 [[options]] == オプション +-l+:: +--verbose+:: ジョブを構成しているパイプラインの要素ごとにプロセス ID と状態とコマンド名を表示します。 +-n+:: +--new+:: 状態が変化してからまだ一度も表示していないジョブだけを表示します。 +-p+:: +--pgid-only+:: ジョブのプロセスグループ ID だけを表示します。 +-r+:: +--running-only+:: 実行中のジョブだけを表示します。 +-s+:: +--stopped-only+:: 停止中のジョブだけを表示します。 [[operands]] == オペランド {{ジョブ}}:: 表示するジョブの{zwsp}link:job.html#jobid[ジョブ ID] です。一つも指定しない場合は全てのジョブを表示します。 + 非 link:posix.html[POSIX 準拠モード]ではジョブ ID の先頭の +%+ は省略できます。 [[exitstatus]] == 終了ステータス エラーがない限り jobs コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Jobs コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX で規定されているオプションは +-l+ と +-p+ だけです。従って link:posix.html[POSIX 準拠モード]ではこれ以外のオプションは使えません。また POSIX 準拠モードでは、+-l+ オプション指定時、プロセスごとではなくジョブごとに状態を表示します。 Yash では、ジョブのプロセスグループ ID はジョブを構成するパイプラインの最初のコマンドのプロセス ID に一致します。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_kill.txt000066400000000000000000000111651354143602500154150ustar00rootroot00000000000000= Kill 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Kill 組込みコマンド dfn:[Kill 組込みコマンド]はプロセスにシグナルを送ります。 [[syntax]] == 構文 - +kill [-{{シグナル}}|-s {{シグナル}}|-n {{シグナル}}] {{プロセス}}...+ - +kill -l [-v] [{{シグナル}}...]+ Kill コマンドでは、{zwsp}link:posix.html[POSIX 準拠モード]であるかどうかにかかわらずオプションはオペランドより先に全て指定しなければなりません。最初のオペランドより後にあるコマンドライン引数は全てオペランドとして解釈します。 [[description]] == 説明 +-l+ オプションを付けずに実行すると、kill コマンドは指定した{{プロセス}}にシグナルを送信します。送信するシグナルの種類は{{シグナル指定オプション}}で指定します。シグナルの種類を指定しない場合は SIGTERM シグナルを送信します。 +-l+ オプションを付けて実行すると、kill コマンドは指定した{{シグナル}}に関する情報を標準出力に出力します。{{シグナル}}を指定しない場合は全てのシグナルに関する情報を表示します。 [[options]] == オプション === シグナル指定オプション +-{{シグナル}}+:: +-s {{シグナル}}+:: +-n {{シグナル}}+:: 送信するシグナルを指定します。{{シグナル}}にはシグナル番号とシグナル名のどちらかを指定します。シグナル番号として 0 を指定すると、シグナルを送ることができるかどうかの判定だけを行い、実際にはシグナルを送信しません。シグナルを名前で指定する際は、大文字と小文字の区別はありません。 シグナル指定オプションは一度に一つまでしか使えません。 === 他のオプション +-l+:: シグナルに関する情報を表示します。 +-v+:: シグナルに関する情報をより詳しく表示します。+-v+ オプションを指定していない場合は単にシグナル名を出力しますが、指定している場合はシグナル番号・シグナル名・シグナルの簡単な説明を出力します。 + このオプションを指定したときは同時に +-l+ も指定してあるとみなします。 [[operands]] == オペランド {{プロセス}}:: シグナルを送信するプロセスをプロセス ID・プロセスグループ ID・link:job.html#jobid[ジョブ ID] のいずれかで指定します。プロセスグループ ID を指定するときは、先頭に負号 (+-+) を付けます。プロセスとして +0+ を指定すると、シェルプロセスが属するプロセスグループを指定したものとみなします。プロセスとして +-1+ を指定すると、全てのプロセスにシグナルを送信します。 {{シグナル}}:: 情報を表示するシグナルの名前または番号です。シグナルによって中断したコマンドの終了ステータスを指定することもできます。 [[exitstatus]] == 終了ステータス エラーがない限り kill コマンドの終了ステータスは 0 です。一つ以上のプロセスにシグナルを送ることができた場合、他にシグナルを送れなかったプロセスがあったとしても終了ステータスは 0 になります。 [[notes]] == 補足 Kill コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 負数に見えるコマンドライン引数の扱いには注意が必要です。例えば +kill -1 -2+ では +-1+ がシグナル指定オプション、+-2+ がオペランドとなるので、番号 1 のシグナルをプロセスグループ 2 に送信します。+kill -- -1 -2+ や +kill -TERM -1 -2+ では +-1+ と +-2+ はどちらもオペランドになります。 POSIX には +-v+ および +-n+ オプションに関する規定はありません。よってこれらのオプションは link:posix.html[POSIX 準拠モード]では使えません。また POSIX は +-s+ オプションの引数としてシグナル番号を指定することを認めていません。POSIX は{{シグナル}}のオペランドとしてシグナルの名前を指定することを認めていません。 POSIX は、シグナル名は +INT+ や +QUIT+ のように最初の SIG を除いた形で指定しなければならないと規定しています。非 link:posix.html[POSIX 準拠モード]の yash では、拡張として SIG を付けた形でも指定できます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_local.txt000066400000000000000000000012671354143602500155560ustar00rootroot00000000000000= Local 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Local 組込みコマンド dfn:[Local 組込みコマンド]はローカル変数を表示・設定します。 [[syntax]] == 構文 - +local [-rxX] [{{name}}[={{value}}]...]+ [[description]] == 説明 Local コマンドは link:_typeset.html[typeset コマンド]と同じですが +-f+ (+--functions+) および +-g+ (+--global+) オプションは使えません。 [[notes]] == 補足 Local コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では local コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_popd.txt000066400000000000000000000031421354143602500154200ustar00rootroot00000000000000= Popd 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Popd 組込みコマンド dfn:[Popd 組込みコマンド]は{zwsp}link:_dirs.html[ディレクトリスタック]からディレクトリを削除し、シェルの作業ディレクトリを戻します。 [[syntax]] == 構文 - +popd [{{インデックス}}]+ [[description]] == 説明 Popd コマンドは{zwsp}link:_dirs.html[ディレクトリスタック]からオペランドで指定したインデックスの要素を削除します。インデックス ++0+ の要素を削除した場合は、新たにインデックス ++0+ の要素となったディレクトリにシェルの作業ディレクトリを変更し、そのディレクトリ名を標準出力に出力します。 [[operands]] == オペランド {{インデックス}}:: 削除するディレクトリスタックの要素のインデックスです。省略すると ++0+ を指定したものとみなします。 [[exitstatus]] == 終了ステータス ディレクトリスタックの要素を正しく削除し作業ディレクトリを変更できた場合、終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 ディレクトリスタックに要素が一つしかない場合はそれ以上要素を削除できないので、エラーになります。 Popd コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では popd コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_printf.txt000066400000000000000000000214521354143602500157640ustar00rootroot00000000000000= Printf 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Printf 組込みコマンド dfn:[Printf 組込みコマンド]はオペランドを整形して出力します。 [[syntax]] == 構文 - +printf {{書式}} [{{値}}...]+ [[description]] == 説明 Printf コマンドはオペランドで指定した{{書式}}に従って{{値}}を整形し、標準出力に出力します。{zwsp}link:_echo.html[Echo コマンド]とは異なり、出力の最後には自動的に改行は付きません。 書式の指定の仕方は C 言語の printf 関数とよく似ています。書式の中では +%+ で始まる変換指定と +\+ で始まるエスケープシーケンスを使用できます。書式に含まれる変換指定とエスケープ以外の文字はそのまま出力します。 [[convspec]] === 変換指定 変換指定はパーセント記号 (+%+) から始まります。 +%%+ 以外の変換指定は、対応する値をとります。変換指定は、値を特定の形式に整形して出力します。変換指定と値は与えられた順番に対応付けられます。値が余った場合は、全ての値を処理し終わるまで書式の整形・出力を繰り返します。値が足りない場合は、空文字列 (文字列に関する変換指定の場合) または 0 (数値に関する変換指定の場合) を仮定します。値が一つも与えられていない場合は、書式は一度だけ出力されます。 利用可能な変換指定は以下の通りです。 +%d+:: +%i+:: 整数の値を (符号付き) 十進整数として出力します。 +%u+:: 整数の値を (符号なし) 十進非負整数として出力します。 +%o+:: 整数の値を (符号なし) 八進非負整数として出力します。 +%x+:: 整数の値を小文字の (符号なし) 十六進非負整数として出力します。 +%X+:: 整数の値を大文字の (符号なし) 十六進非負整数として出力します。 +%f+:: 実数の値を小文字の (符号付き) 小数として出力します。 +%F+:: 実数の値を大文字の (符号付き) 小数として出力します。 +%e+:: 実数の値を小文字の (符号付き) 指数表記小数で出力します。 +%E+:: 実数の値を大文字の (符号付き) 指数表記小数で出力します。 +%g+:: 値の大きさや精度に応じて +%f+ と +%e+ のどちらかの形式で出力します。 +%G+:: 値の大きさや精度に応じて +%F+ と +%E+ のどちらかの形式で出力します。 +%c+:: 文字列の値の最初の文字を出力します。 +%s+:: 文字列の値をそのまま出力します。 +%b+:: 文字列の値を、エスケープシーケンスを解釈しながら出力します。ここで使えるエスケープシーケンスは link:_echo.html[echo コマンド]で使えるエスケープシーケンスと同じです。 +%%+:: パーセント記号 (+%+) を出力します。 +%g+ と +%G+ では、小数の指数部が -5 以上精度以下の時に +%f+ または +%F+ を、それ以外の時に +%e+ または +%E+ を使用します。 +%%+ 以外の変換指定では、最初の +%+ の直後に変換指定フラグ・フィールド幅・精度をこの順で指定できます。これらを指定することで出力の形式を細かく調整できます。 [[convspec-flags]] ==== 変換指定フラグ 指定できる変換指定フラグは以下の通りです。フラグを複数指定しても構いません。 マイナス (+-+):: このフラグを指定すると、指定したフィールド幅の中で値を左に寄せて出力します。このフラグを指定しない場合、値は右に寄ります。 プラス (+++):: 数値の符号 (正号または負号) を必ず出力します。 空白文字 (+ +):: 出力する数値に符号 (正号または負号) が付かない場合は、符号の代わりに空白文字を出力します。 +#+:: 値を別形式で出力します。 変換指定が +%o+ の場合、出力する八進数の先頭に必ず一桁以上の 0 が付くように、必要に応じて 0 を付加します。 変換指定が +%x+ (または +%X+) の場合、値が 0 でなければ数値の先頭に +0x+ (または +0X+) を付加します。 変換指定が +%e+, +%E+, +%f+, +%F+, +%g+, +%G+ の場合、小数点の後に数字がない場合でも小数点を省略しないようにします。また変換指定が +%g+, +%G+ の場合、小数点の後に 0 以外の数字がない場合でも 0 を省略しないようにします。 ゼロ (+0+):: 変換指定が +%d+, +%i+, +%u+, +%o+, +%x+, +%X+, +%e+, +%E+, +%f+, +%F+, +%g+, +%G+ の場合、出力が指定したフィールド幅いっぱいになるまで数値の先頭に 0 を付加します。 + マイナスフラグが指定されている場合、このフラグは無視されます。 + 変換指定が +%d+, +%i+, +%u+, +%o+, +%x+, +%X+ で、精度が指定されている場合、このフラグは無視されます。 [[convspec-width]] ==== フィールド幅 フィールド幅は、先頭に 0 の付かない十進整数の形で指定します。 フィールド幅は出力の最低バイト数を指示します。出力のバイト数がフィールド幅に満たないときは、バイト数がフィールド幅に一致するまで空白文字を付加します。 [[convspec-precision]] ==== 精度 精度は、ピリオド (+.+) の直後に十進整数を置いたものの形で指定します。ピリオドの後に整数がなければ、0 が指定してあるものとみなします。 変換指定が +%d+, +%i+, +%u+, +%o+, +%x+, +%X+ の場合、精度は出力の最低桁数を指示します。数値が最低桁数に満たない場合は最低桁数に達するまで先頭に 0 を付加します。精度が指定されていない場合、精度は 1 とみなします。 変換指定が +%e+, +%E+, +%f+, +%F+ の場合、精度は小数点以降の桁数を指示します。精度が指定されていない場合、精度は 6 とみなします。 変換指定が +%g+, +%G+ の場合、精度は数値の最大有効桁数を指示します。精度が指定されていない場合、精度は 6 とみなします。 変換指定が +%s+, +%b+ の場合、精度は出力する文字列の最大バイト数を指示します。精度が指定されていない場合、精度は無限大とみなします。 [[convspec-examples]] ==== 例 変換指定 +%f+ にゼロフラグを指定し、フィールド幅に 8、精度に 3 を指定する場合、最終的な変換指定は +%08.3f+ となります。この変換指定に対して値 12.34 を与えると、出力は +0012.340+ となります。 [[escapes]] === エスケープシーケンス 書式の中で使えるエスケープシーケンスは以下の通りです。 +\a+:: ベル文字 (ASCII コード番号 7) +\b+:: バックスペース (ASCII コード番号 8) +\f+:: フォームフィード (ASCII コード番号 12) +\n+:: 改行文字 (ASCII コード番号 10) +\r+:: 復帰文字 (ASCII コード番号 13) +\t+:: 水平タブ (ASCII コード番号 9) +\v+:: 垂直タブ (ASCII コード番号 11) +\\+:: バックスラッシュ +\"+:: 二重引用符 +\'+:: 一重引用符 (アポストロフィー) +\{{xxx}}+:: 八進数 {{xxx}} (最大三桁) で表わされるコード番号の文字 [[operands]] == オペランド {{書式}}:: 出力する文字列の書式です。 {{値}}:: 変換指定が出力する値 (数値または文字列) です。 + 数値を値として指定する際、一重または二重引用符の後に何か文字を置いたものを指定することで、その文字のコード番号を数値として指定できます。例えば +3+ という文字のコード番号が 51 ならば、 `printf '%d' '"3'` は +51+ を出力します。 [[exitstatus]] == 終了ステータス エラーがない限り printf コマンドの終了ステータスは 0 です。 [[notes]] == 補足 POSIX では、マルチバイト文字の扱いについて厳密に定義していません。+%s+ 変換指定で精度を指定した場合や、+%c+ 変換指定を使用する場合、値にマルチバイト文字が含まれていると適切な出力が得られないかもしれません。Yash では、マルチバイト文字は全てワイド文字に変換してから処理するので、マルチバイト文字の一部のバイトだけが出力されるようなことはありません。 シェルが非 link:posix.html[POSIX 準拠モード]で、システム上で ``long double'' 浮動小数点数が使用可能な場合は、実数の変換指定は ``long double'' で処理されます。それ以外の場合は ``double'' で処理されます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_pushd.txt000066400000000000000000000046721354143602500156120ustar00rootroot00000000000000= Pushd 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Pushd 組込みコマンド dfn:[Pushd 組込みコマンド]は{zwsp}link:_dirs.html[ディレクトリスタック]にディレクトリを追加し、シェルの作業ディレクトリをそのディレクトリに変更します。 [[syntax]] == 構文 - +pushd [-L|-P] [{{ディレクトリ}}]+ [[description]] == 説明 Pushd コマンドは link:_cd.html[cd コマンド]と同様に、シェルの作業ディレクトリをオペランドで指定したディレクトリに変更します。作業ディレクトリの変更に成功すると、新しい作業ディレクトリを{zwsp}link:_dirs.html[ディレクトリスタック]に追加します。 [[options]] == オプション link:_cd.html#options[Cd コマンドで使えるオプション]に加えて以下のオプションが pushd コマンドで使えます。 +--remove-duplicates+:: 新しい作業ディレクトリが既にディレクトリスタックに入っている場合は、元々入っていた要素を削除して重複をなくします。 [[operands]] == オペランド {{ディレクトリ}}:: 新しい作業ディレクトリのパス名です。絶対パスまたは元の作業ディレクトリからの相対パスで指定します。 + この値がハイフン一つ (+-+) の場合、{zwsp}link:params.html#sv-oldpwd[+OLDPWD+ 変数]の値が指定されたものとみなします。 + この値が符号付き整数の場合、その整数を{zwsp}link:_dirs.html[ディレクトリスタック]の要素のインデックスとみなして、その要素が表すディレクトリが指定されたものとみなします (指定された要素はディレクトリスタックから削除されます)。 + このオペランドが与えられていない場合、インデックス ++1+ が指定されたものとみなします (+--default-directory+ オプションを指定した場合を除く)。 [[exitstatus]] == 終了ステータス 作業ディレクトリを正しく変更しディレクトリスタックに追加できた場合、終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 Pushd コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では pushd コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_pwd.txt000066400000000000000000000025561354143602500152600ustar00rootroot00000000000000= Pwd 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Pwd 組込みコマンド dfn:[Pwd 組込みコマンド]はシェルの現在の作業ディレクトリを表示します。 [[syntax]] == 構文 - +pwd [-L|-P]+ [[description]] == 説明 Pwd コマンドはシェルの現在の作業ディレクトリを絶対パスで標準出力に出力します。 [[options]] == オプション +-L+:: +--logical+:: link:params.html#sv-pwd[+PWD+ 変数]の値が現在の作業ディレクトリの絶対パスで、中に +.+ や +..+ を含んでいなければ、それを出力します。それ以外の場合は +-P+ を指定した場合と同様に出力します。 +-P+:: +--physical+:: 現在の作業ディレクトリの絶対パスを、中にシンボリックリンクを含まないかたちで出力します。 +-L+ (+--logical+) オプションと +-P+ (+--physical+) オプションの両方を指定した場合、後に指定したほうを優先します。どちらも指定していない場合は、+-L+ を指定したものとみなします。 [[exitstatus]] == 終了ステータス エラーがない限り pwd コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Pwd コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_read.txt000066400000000000000000000071161354143602500153760ustar00rootroot00000000000000= Read 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Read 組込みコマンド dfn:[Read 組込みコマンド]は標準入力から行を読み込み変数に代入します。 [[syntax]] == 構文 - +read [-Aer] [-P|-p] {{変数名}}...+ [[description]] == 説明 Read コマンドは標準入力から一行の文字列を読み込み、それを{zwsp}link:params.html#variables[変数]に代入します。 +-r+ (+--raw-mode+) オプションを付けると、行内のバックスラッシュは通常の文字と同様に扱われます。 +-r+ (+--raw-mode+) オプションを付けない場合、読み込んだ文字列の中のバックスラッシュ (+\+) は{zwsp}link:syntax.html#quotes[引用符]として働きます。バックスラッシュが行末にあるときは行の連結を行います。{zwsp}link:interact.html[対話モード]のシェルが 2 行目以降を読み込むとき、標準入力が端末ならば link:params.html#sv-ps2[+PS2+ 変数]の値がプロンプトとして出力されます。 読み込んだ文字列は、{zwsp}link:expand.html#split[単語分割]によって分割します。分割後の各文字列が、それぞれオペランドで指定された変数の値に順に設定されます。指定された変数の数より分割結果のほうが多い場合は、最後の変数に残りの分割結果の全てが入ります。分割結果の数より指定された変数のほうが多い場合は、余った変数には空文字列が入ります。 [[options]] == オプション +-A+:: +--array+:: 最後に指定した変数を{zwsp}link:params.html#arrays[配列]にします。分割後の各文字列が配列の要素として設定されます。 +-e+:: +--line-editing+:: 読み込みに{zwsp}link:lineedit.html[行編集]を使用します。 + 行編集が有効になるには以下の条件が全て満たされている必要があります: + - シェルが{zwsp}link:interact.html[対話モード]である。 - link:_set.html#so-vi[vi] または link:_set.html#so-emacs[emacs] オプションが有効になっている。 - 標準入力と標準エラーが端末である。 +-P+:: +--ps1+:: シェルが対話モードで標準入力が端末ならば、(最初の) 行を読み込む前に link:params.html#sv-ps1[+PS1+ 変数]をプロンプトとしてを表示します。 +-p {{プロンプト}}+:: +--prompt={{プロンプト}}+:: シェルが対話モードで標準入力が端末ならば、(最初の) 行を読み込む前に{{プロンプト}}を表示します。 +-r+:: +--raw-mode+:: 読み込んだ文字列の中のバックスラッシュを引用符として扱わないようにします。 [[operands]] == オペランド {{変数名}}:: 読み込んだ文字列を格納する変数の名前です。 [[exitstatus]] == 終了ステータス エラーがない限り read コマンドの終了ステータスは 0 です。 なお、行を完全に読み込む前に入力が終端に達した時は終了ステータスは非 0 になります。 [[notes]] == 補足 Read コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX には +-A+ (+--array+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 +PS1+ 変数をプロンプトとして表示する際、{zwsp}link:params.html#sv-ps1r[+PS1R+] および link:params.html#sv-ps1s[+PS1S+] 変数も使用されます。 +PS2+ についても同様です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_readonly.txt000066400000000000000000000022041354143602500162710ustar00rootroot00000000000000= Readonly 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Readonly 組込みコマンド dfn:[Readonly 組込みコマンド]は読み取り専用の{zwsp}link:params.html#variables[変数]または{zwsp}link:exec.html#function[関数]を表示・設定します。 [[syntax]] == 構文 - +readonly [-pxX] [{{変数}}[={{値}}]...]+ - +readonly -f[p] [{{変数}}...]+ [[description]] == 説明 Readonly コマンドは link:_typeset.html[typeset コマンド]に +-gr+ オプションを付けたものと同じです。その他オプション・オペランド・終了ステータスは typeset コマンドと同様です。 [[notes]] == 補足 readonly コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX には readonly コマンドに関する規定はありますが、オプションは +-p+ しか規定がありません。その他のオプションは link:posix.html[POSIX 準拠モード]では使えません。また POSIX は +-p+ オプションをオペランドとともに使うことを認めていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_return.txt000066400000000000000000000060041354143602500157750ustar00rootroot00000000000000= Return 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Return 組込みコマンド dfn:[Return 組込みコマンド]は現在実行中の{zwsp}link:exec.html#function[関数]またはスクリプトの実行を終了します。 [[syntax]] == 構文 - +return [-n] [{{終了ステータス}}]+ [[description]] == 説明 +-n+ (+--no-return+) オプションを付けずに return コマンドを実行すると、以下のうち当てはまる動作を行います: - link:exec.html#function[関数]の実行中の場合は、その関数の実行を終了します。 - link:_dot.html[ドットコマンド]でファイルを開いてコマンドを実行している途中の場合は、そのファイルの読み込み・実行を終了します。 - link:invoke.html#init[シェルの初期化スクリプト]を実行中の場合は、そのスクリプトの実行を終了します。 - link:_trap.html[トラップ]を実行中の場合は、そのトラップの実行を終了します。(ただし他のシグナルに関するトラップの実行が控えている場合はそれらは通常通り実行されます。) - これ以外の場合は、(link:interact.html[対話モード]のときを除いて) シェルは終了します。 +-n+ (+--no-return+) オプションを付けて return コマンドを実行すると、return コマンドはただ単にオペランドで指定されている終了ステータスを返します。 [[options]] == オプション +-n+:: +--no-return+:: コマンドの実行を中断しません。 [[operands]] == オペランド {{終了ステータス}}:: Return コマンドの終了ステータスを指定する 0 以上の自然数です。 + このオペランドが与えられていない場合は、return コマンドの直前に実行されたコマンドの終了ステータスを用います (ただし{zwsp}link:_trap.html[トラップ]を実行中の場合はトラップに入る直前のコマンドの終了ステータス)。 [[exitstatus]] == 終了ステータス Return コマンドの終了ステータスはオペランドで与えられた値です。Return コマンドの終了ステータスは return コマンドが終了する関数・ドットコマンド・初期化スクリプト・シェル自身の終了ステータスにもなります。 [[notes]] == 補足 Return コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX では、{{終了ステータス}}の値は 0 以上 256 未満でなければならないとしています。Yash では拡張として 256 以上の値も受け付けるようになっています。 POSIX では関数あるいはドットコマンドの実行中以外における return コマンドの動作を定めていません。 POSIX には +-n+ (+--no-return+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_set.txt000066400000000000000000000335501354143602500152570ustar00rootroot00000000000000= Set 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Set 組込みコマンド dfn:[Set 組込みコマンド]はシェルのオプションの設定と{zwsp}link:params.html#positional[位置パラメータ]の変更を行います。 [[syntax]] == 構文 - +set [{{オプション}}...] [{{オペランド}}...]+ - +set -o+ - +set +o+ Set コマンドでは、{zwsp}link:posix.html[POSIX 準拠モード]であるかどうかにかかわらずオプションはオペランドより先に全て指定しなければなりません。最初のオペランドより後にあるコマンドライン引数は全てオペランドとして解釈します。 [[description]] == 説明 コマンドライン引数を一切与えずに set コマンドを実行すると、現在シェルに設定されている全ての{zwsp}link:params.html#variables[変数]の一覧をアルファベット順で (コマンドとして解釈可能な形式で) 標準出力に出力します。 +-o+ を唯一のコマンドライン引数として与えた場合は現在のシェルのオプション設定を一覧にして標準出力に出力します。++o+ を唯一のコマンドライン引数として与えた場合も同様ですが、この場合はコマンドとして解釈可能な形式で出力します。 これ以外の場合は、set コマンドは以下のようにシェルのオプションの設定と{zwsp}link:params.html#positional[位置パラメータ]の変更のどちらかまたは両方の動作を行います。 [[options]] == オプション オプションが一つ以上与えられている場合、set コマンドはそれらの有効・無効を切り替えます。通常の形式でオプションを与えると、そのオプションは有効になります。オプションの先頭のハイフン (+-+) の代わりにプラス (+++) を付けて指定すると、そのオプションは無効になります。例えば +-m+ や +-o monitor+ や +--monitor+ はシェルのジョブ制御を有効にし、逆に ++m+ や ++o monitor+ や +++monitor+ はジョブ制御を無効にします。 link:builtin.html#argsyntax[長いオプション]の名前に含まれる英数字以外の文字は無視され、大文字と小文字の区別はありません。例えば +--Le-Comp-Debug+ は +--lecompdebug+ に同じです。また長いオプションの名前の先頭に +no+ を付けることで、オプションの有効・無効を逆転することができます。例えば +--noallexport+ は +++allexport+ に同じく、また +++nonotify+ は +--notify+ に同じです。 オプションは以下に挙げる形式で指定することができます: - 長いオプション (例: +--allexport+) - 引数としてオプション名を指定した +-o+ オプション (例: +-o allexport+) - 一文字のオプション (例: +-a+) ただし全てのオプションが一文字のオプションで指定できるわけではありません。 利用可能なオプションは以下のとおりです: [[so-allexport]]all-export (+-a+):: このオプションが有効な時、{zwsp}link:params.html#variables[変数]に代入をするとその変数は自動的に{zwsp}link:params.html#variables[エクスポート]対象になります。 [[so-braceexpand]]brace-expand:: このオプションは{zwsp}link:expand.html#brace[ブレース展開]を有効にします。 [[so-caseglob]]case-glob:: このオプションが有効な時、{zwsp}link:expand.html#glob[パス名展開]におけるパターンマッチングは大文字と小文字を区別して行います。このオプションはシェルの起動時に最初から有効になっています。 [[so-clobber]]clobber (`+C`):: このオプションを無効にすると、 +>+ 演算子による{zwsp}link:redir.html[リダイレクト]で既存のファイルを上書きすることはできなくなります。このオプションはシェルの起動時に最初から有効になっています。 [[so-curasync]]cur-async:: [[so-curbg]]cur-bg:: [[so-curstop]]cur-stop:: これらのオプションは現在のジョブの選択の仕方に影響します。(link:job.html#jobid[ジョブ ID] 参照)。これらのオプションはシェルの起動時に最初から有効になっています。 [[so-dotglob]]dot-glob:: このオプションが有効な時、{zwsp}link:expand.html#glob[パス名展開]においてファイル名の先頭のピリオドを特別に扱いません。 [[so-emacs]]emacs:: このオプションは emacs 風{zwsp}link:lineedit.html[行編集]を有効にします。 [[so-emptylastfield]]empty-last-field:: このオプションが有効な時、{zwsp}link:expand.html#split[単語分割]で最後の単語が空になっても削除しません。 [[so-errexit]]err-exit (+-e+):: このオプションが有効な時、実行した{zwsp}link:syntax.html#pipelines[パイプライン]の終了ステータスが 0 でなければ、シェルは直ちに終了します。ただし、以下の抑止条件に当てはまる場合を除きます。 - そのコマンドが link:syntax.html#if[if 文]の分岐や link:syntax.html#while-until[while/until 文]のループ条件の判定に使われる場合 - パイプラインの先頭に +!+ が付いている場合 - パイプラインがサブシェル{zwsp}link:syntax.html#grouping[グルーピング]以外の単独の{zwsp}link:syntax.html#compound[複合コマンド]から構成される場合 [[so-errreturn]]err-return:: このオプションは err-exit オプションと同様ですが、終了ステータスが 0 でないときにシェルが終了する代わりに link:_return.html[return 組込みコマンド]が発動します。 Err-exit オプションと異なり、抑止条件は{zwsp}link:exec.html#function[関数]・サブシェル{zwsp}link:syntax.html#grouping[グルーピング]・{zwsp}link:_dot.html[スクリプトファイル]の中では無視されます。 [[so-exec]]exec (`+n`):: このオプションが無効な時、シェルはコマンドの解釈だけを行い、実際にはコマンドを実行しません。このオプションはシェルスクリプトの文法チェックをするのに便利です。このオプションはシェルの起動時に最初から有効になっています。{zwsp}link:interact.html[対話モード]では、このオプションに関わらずコマンドは常に実行されます。 [[so-extendedglob]]extended-glob:: このオプションは{zwsp}link:expand.html#glob[パス名展開]における拡張機能を有効にします。 [[so-forlocal]]for-local:: link:syntax.html#for[For ループ]が{zwsp}link:exec.html#function[関数]の中で実行されるとき、このオプションが有効ならばループの変数は{zwsp}link:exec.html#localvar[ローカル変数]として代入されます。このオプションはシェルの起動時に最初から有効になっています。{zwsp}link:posix.html[POSIX 準拠モード]ではこのオプションに関係なく for ループの変数は通常の変数として代入されます。 [[so-glob]]glob (`+f`):: このオプションが有効なときはシェルは{zwsp}link:expand.html#glob[パス名展開]を行います。このオプションはシェルの起動時に最初から有効になっています。 [[so-hashondef]]hash-on-def (+-h+):: このオプションが有効なとき{zwsp}link:exec.html#function[関数]を定義すると、直ちにその関数内で使われる各コマンドの link:exec.html#search[PATH 検索]を行いコマンドのパス名を記憶します。 [[so-histspace]]hist-space:: このオプションが有効な時は空白で始まる行は{zwsp}link:interact.html#history[コマンド履歴]に自動的に追加しません。 [[so-ignoreeof]]ignore-eof:: このオプションが有効な時、{zwsp}link:interact.html[対話モード]のシェルに EOF (入力の終わり) が入力されてもシェルはそれを無視してコマンドの読み込みを続けます。これにより、誤って Ctrl-D を押してしまってもシェルは終了しなくなります。 [[so-lealwaysrp]]le-always-rp:: [[so-lecompdebug]]le-comp-debug:: [[so-leconvmeta]]le-conv-meta:: [[so-lenoconvmeta]]le-no-conv-meta:: [[so-lepredict]]le-predict:: [[so-lepredictempty]]le-predict-empty:: [[so-lepromptsp]]le-prompt-sp:: [[so-levisiblebell]]le-visible-bell:: これらのオプションは{zwsp}link:lineedit.html[行編集]の動作に影響します。{zwsp}link:lineedit.html#options[行編集のオプション]を参照してください。 [[so-markdirs]]mark-dirs:: このオプションが有効な時、{zwsp}link:expand.html#glob[パス名展開]の展開結果においてディレクトリを表すものの末尾にスラッシュを付けます。 [[so-monitor]]monitor (+-m+):: このオプションは{zwsp}link:job.html[ジョブ制御]を有効にします。シェルを{zwsp}link:interact.html[対話モード]で起動したときこのオプションは自動的に有効になります。 [[so-notify]]notify (+-b+):: このオプションが有効な時は、バックグラウンドの{zwsp}link:job.html[ジョブ]の実行状態が変化するとシェルは直ちにそれを標準エラーに報告します。このオプションは notifyle オプションより優先します。 [[so-notifyle]]notify-le:: このオプションは notify オプションとほぼ同じですが、{zwsp}link:lineedit.html[行編集]を行っている最中のみジョブの状態変化を報告します。 [[so-nullglob]]null-glob:: このオプションが有効な時、{zwsp}link:expand.html#glob[パス名展開]でマッチするパス名がないとき元のパターンは残りません。 [[so-pipefail]]pipe-fail:: このオプションが有効な時、{zwsp}link:syntax.html#pipelines[パイプライン]の全てのコマンドの終了ステータスが 0 の時のみパイプラインの終了ステータスが 0 になります。 [[so-posixlycorrect]]posixly-correct:: このオプションは link:posix.html[POSIX 準拠モード]を有効にします。 [[so-traceall]]trace-all:: このオプションは、補助コマンド実行中も <>を機能させるかどうかを指定します。補助コマンドとは、 link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+]、 link:params.html#sv-prompt_command[+PROMPT_COMMAND+]、および link:params.html#sv-yash_after_cd[+YASH_AFTER_CD+] 変数の値として定義され、特定のタイミングで解釈・実行されるコマンドです。 このオプションはシェルの起動時に最初から有効になっています。 [[so-unset]]unset (`+u`):: このオプションが有効な時、{zwsp}link:expand.html#params[パラメータ展開]で存在しない変数を展開すると空文字列に展開され、link:expand.html#arith[数式展開]で存在しない変数を使用すると 0 とみなされます。オプションが無効な時、存在しない変数を使用するとエラーになります。このオプションはシェルの起動時に最初から有効になっています。 [[so-verbose]]verbose (+-v+):: このオプションが有効な時、シェルは読み込んだコマンドをそのまま標準エラーに出力します。 [[so-vi]]vi:: このオプションは vi 風{zwsp}link:lineedit.html[行編集]を有効にします。{zwsp}link:interact.html[対話モード]が有効で標準入力と標準エラーがともに端末ならばこのオプションはシェルの起動時に自動的に有効になります。 [[so-xtrace]]x-trace (+-x+):: このオプションが有効な時、コマンドを実行する前に{zwsp}link:expand.html[展開]の結果を標準エラーに出力します。この出力は、各行頭に link:params.html#sv-ps4[+PS4+ 変数]の値を{zwsp}link:expand.html[展開]した結果を付けて示されます。 <>も参照してください。 [[operands]] == オペランド Set コマンドにオペランドが与えられている場合またはオプションとオペランドを区切るハイフン二つ (+--+, link:builtin.html#argsyntax[コマンドの引数の構文]参照) がコマンドライン引数に入っている場合は、現在の{zwsp}link:params.html#positional[位置パラメータ]は削除され、与えられたオペランドがそれぞれ新しく位置パラメータになります。ハイフン二つが与えられていてかつオペランドがない場合は位置パラメータはなくなります。 [[exitstatus]] == 終了ステータス オプションの指定が間違っている場合を除き、set コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Set コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX 規格に定義されているオプションは限られています。規格の定義では、 - +--allexport+ などの長いオプションは使えません。 - オプション名に +no+ を付けてオプションを無効にすることはできません。 - オプション名に大文字や英字でない記号は使えません。 規格に定義されているオプションは以下のとおりです: - +-a+, +-o allexport+ - +-e+, +-o errexit+ - +-m+, +-o monitor+ - +-C+, +-o noclobber+ - +-n+, +-o noexec+ - +-f+, +-o noglob+ - +-b+, +-o notify+ - +-u+, +-o nounset+ - +-v+, +-o verbose+ - +-x+, +-o xtrace+ - +-h+ - +-o ignoreeof+ - +-o nolog+ - +-o vi+ POSIX ではこのほかに、{zwsp}link:syntax.html#funcdef[関数定義]を{zwsp}link:interact.html#history[コマンド履歴]に登録しないようにする +-o nolog+ オプションを規定していますが、yash はこれをサポートしていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_shift.txt000066400000000000000000000037431354143602500156020ustar00rootroot00000000000000= Shift 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Shift 組込みコマンド dfn:[Shift 組込みコマンド]は{zwsp}link:params.html#positional[位置パラメータ]または{zwsp}link:params.html#arrays[配列]の要素のいくつかを削除します。 [[syntax]] == 構文 - +shift [-A {{配列名}}] [{{個数}}]+ [[description]] == 説明 Shift コマンドは{zwsp}link:params.html#positional[位置パラメータ]または{zwsp}link:params.html#arrays[配列]の要素のうち最初のいくつかを削除します。削除するパラメータ・要素の数はオペランドで指定します。 [[options]] == オプション +-A {{配列}}+:: +--array={{配列}}+:: 位置パラメータではなく{{配列}}の最初の要素を削除します。 [[operands]] == オペランド {{個数}}:: 削除する位置パラメータまたは配列の要素の個数を指示する整数です。 + 実際の位置パラメータ・要素の個数より大きい数を指定するとエラーになります。省略すると 1 を指定したものとみなします。負数を指定すると最初ではなく最後の位置パラメータまたは配列要素を削除します。 [[exitstatus]] == 終了ステータス エラーがない限り shift コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Shift コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 位置パラメータの個数は{zwsp}link:params.html#special[特殊パラメータ +#+] によって知ることができます。配列の要素の個数は +${{{配列}}[#]}+ によって知ることができます。 POSIX には +-A+ (+--array+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 POSIX 準拠モードでは負の{{個数}}は指定できません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_suspend.txt000066400000000000000000000032161354143602500161410ustar00rootroot00000000000000= Suspend 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Suspend 組込みコマンド dfn:[Suspend 組込みコマンド]はシェルを停止 (サスペンド) します。 [[syntax]] == 構文 - +suspend [-f]+ [[description]] == 説明 Suspend コマンドはシェルプロセスが属するプロセスグループ内のすべてのプロセスに対して SIGSTOP シグナルを送信します。これにより、シグナルを送られた各プロセス (シェル自身を含む) は停止 (サスペンド) 状態になります。停止状態になったプロセスは SIGCONT シグナルを受信すると実行を再開します。 シェルが{zwsp}link:interact.html[対話モード]で、さらにシェルプロセスのプロセスグループ ID がセッションリーダーのプロセス ID に等しいときは、+-f+ (+--force+) オプションを付けない限りシェルは警告を表示し、シグナルを送信しません。これはシェルが停止した後実行を再開させることができなくなってしまうのを未然に防ぐためです。 [[options]] == オプション +-f+:: +--force+:: 警告を無視してシェルを停止します。 [[exitstatus]] == 終了ステータス Suspend コマンドの終了ステータスは、SIGSTOP シグナルをシェルに正しく送信できたときは 0、それ以外なら非 0 です。 [[notes]] == 補足 Suspend コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では suspend コマンドの動作は規定されていません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_test.txt000066400000000000000000000206631354143602500154440ustar00rootroot00000000000000= Test 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Test 組込みコマンド dfn:[Test 組込みコマンド]は引数で指定した内容の判定を行います。 [[syntax]] == 構文 - +test {{判定式}}+ - +[ {{判定式}} ]+ Test コマンドはオプションとオペランドとを区別しません。コマンドライン引数は全て{{判定式}}として解釈します。コマンドが +[+ の名前で実行された時は、判定式の後に +]+ が必要です。 [[description]] == 説明 Test コマンドは引数で与えられた{{判定式}}を評価し、結果が真ならば 0 の終了ステータスを、偽ならば 1 の終了ステータスで終了します。判定式は何種類かの演算子とそれに対する被演算子とからなります。 ファイルに関する判定を行う単項演算子は以下の通りです。指定したファイルがシンボリックリンクの場合、そのシンボリックリンクが指している先のファイルについて判定を行います (+-h+, +-L+ 演算子を除く)。 +-b {{ファイル}}+:: {{ファイル}}がブロックスペシャルファイルかどうか +-c {{ファイル}}+:: {{ファイル}}がキャラクタスペシャルファイルかどうか +-d {{ファイル}}+:: {{ファイル}}がディレクトリかどうか +-e {{ファイル}}+:: {{ファイル}}が存在するかどうか +-f {{ファイル}}+:: {{ファイル}}が通常のファイルかどうか +-G {{ファイル}}+:: {{ファイル}}のグループ ID がシェルの実効グループ ID に等しいかどうか +-g {{ファイル}}+:: {{ファイル}}の set-group-ID ビットが設定されているかどうか +-h {{ファイル}}+:: +-L {{ファイル}}+ に同じ +-k {{ファイル}}+:: {{ファイル}}の sticky ビットが設定されているかどうか +-L {{ファイル}}+:: {{ファイル}}がシンボリックリンクかどうか +-N {{ファイル}}+:: {{ファイル}}の最終変更日時が最終アクセス日時より後かどうか +-O {{ファイル}}+:: {{ファイル}}のユーザ ID がシェルの実効ユーザ ID に等しいかどうか +-p {{ファイル}}+:: {{ファイル}}が FIFO (名前付きパイプ) かどうか +-r {{ファイル}}+:: {{ファイル}}が読み込み可能かどうか +-S {{ファイル}}+:: {{ファイル}}がソケットかどうか +-s {{ファイル}}+:: {{ファイル}}サイズが空でないかどうか +-u {{ファイル}}+:: {{ファイル}}の set-user-ID ビットが設定されているかどうか +-w {{ファイル}}+:: {{ファイル}}が書き込み可能かどうか +-x {{ファイル}}+:: {{ファイル}}が実行可能かどうか ファイル記述子に関する判定を行う単項演算子は以下の通りです。 +-t {{ファイル記述子}}+:: {{ファイル記述子}}が端末かどうか (ファイル記述子は 0 以上の自然数で指定します) 文字列に関する判定を行う単項演算子は以下の通りです。 +-n {{文字列}}+:: 文字列が空文字列でないかどうか +-z {{文字列}}+:: 文字列が空文字列かどうか link:_set.html[シェルのオプション]に関する判定を行う単項演算子は以下の通りです。 +-o ?{{オプション}}+:: {{オプション}}が正しいオプション名であるかどうか +-o {{オプション}}+:: {{オプション}}が正しいオプション名であり、かつオプションが有効に設定されているかどうか ファイルに関する判定を行う二項演算子は以下の通りです (存在しないファイルは他のファイルより古いとみなします)。 +{{ファイル1}} -nt {{ファイル2}}+:: {{ファイル1}}の更新時刻が{{ファイル2}}より新しいかどうか +{{ファイル1}} -ot {{ファイル2}}+:: {{ファイル1}}の更新時刻が{{ファイル2}}より古いかどうか +{{ファイル1}} -ef {{ファイル2}}+:: 二つのファイルが互いのハードリンクであるかどうか 文字列に関する判定を行う二項演算子は以下の通りです。 +{{文字列1}} = {{文字列2}}+:: +{{文字列1}} == {{文字列2}}+:: 二つの文字列が同じかどうか +{{文字列1}} != {{文字列2}}+:: 二つの文字列が異なるかどうか 以下の二項演算子は現在のロケールの辞書式順序に従って文字列を比較します。 +{{文字列1}} === {{文字列2}}+:: 二つの文字列が同じかどうか +{{文字列1}} !== {{文字列2}}+:: 二つの文字列が異なるかどうか +{{文字列1}} < {{文字列2}}+:: {{文字列1}} が {{文字列2}} よりも順序が手前かどうか +{{文字列1}} <= {{文字列2}}+:: {{文字列1}} が {{文字列2}} よりも順序が手前または同じかどうか +{{文字列1}} > {{文字列2}}+:: {{文字列1}} が {{文字列2}} よりも順序が後かどうか +{{文字列1}} >= {{文字列2}}+:: {{文字列1}} が {{文字列2}} よりも順序が後または同じかどうか パターンマッチングを行う二項演算子は以下の通りです。 +{{文字列}} =~ {{パターン}}+:: 拡張正規表現{{パターン}}が{{文字列}}(の一部)にマッチするかどうか 整数に関する判定を行う二項演算子は以下の通りです。 +{{v1}} -eq {{v2}}+:: {{v1}} と {{v2}} が等しいかどうか +{{v1}} -ne {{v2}}+:: {{v1}} と {{v2}} が異なるかどうか +{{v1}} -gt {{v2}}+:: {{v1}} が {{v2}} よりも大きいかどうか +{{v1}} -ge {{v2}}+:: {{v1}} が {{v2}} 以上かどうか +{{v1}} -lt {{v2}}+:: {{v1}} が {{v2}} よりも小さいかどうか +{{v1}} -le {{v2}}+:: {{v1}} が {{v2}} 以下かどうか バージョン番号を表す文字列に関する判定を行う二項演算子は以下の通りです。文字列のバージョン番号としての比較のしかたは後述します。 +{{v1}} -veq {{v2}}+:: {{v1}} と {{v2}} が等しいかどうか +{{v1}} -vne {{v2}}+:: {{v1}} と {{v2}} が異なるかどうか +{{v1}} -vgt {{v2}}+:: {{v1}} が {{v2}} よりも大きいかどうか +{{v1}} -vge {{v2}}+:: {{v1}} が {{v2}} 以上かどうか +{{v1}} -vlt {{v2}}+:: {{v1}} が {{v2}} よりも小さいかどうか +{{v1}} -vle {{v2}}+:: {{v1}} が {{v2}} 以下かどうか 他の判定式を組み合わせてより複雑な判定式を作る演算子は以下の通りです。 +! {{判定式}}+:: {{判定式}}が偽かどうか (判定式の真偽を逆転します) +( {{判定式}} )+:: {{判定式}}が真かどうか (判定式の構文上の優先順位を高くします) +{{判定式1}} -a {{判定式2}}+:: 二つの判定式が両方とも真かどうか +{{判定式1}} -o {{判定式2}}+:: 二つの判定式の少なくとも片方が真かどうか 判定式が空の場合、結果は偽とみなします。判定式が (演算子の付いていない) 文字列一つの場合、その文字列が空文字列でないかどうかを判定します。 [[version-compare]] === バージョン番号の比較 文字列のバージョン番号としての比較は、基本的には現在のロケール情報に従った辞書式順序で行います。ただし、連続する数字は一つの自然数として比較します。また数字とそれ以外の文字との比較では常に数字の方が大きいとみなします。 例えば、+0.1.2-3+ と +00.001.02-3+ は等しく、+0.2.1+ と +0.10.0+ とでは後者の方が大きいと判定されます。 [[exitstatus]] == 終了ステータス Test コマンドの終了ステータスは、{{判定式}}の評価結果が真ならば 0、偽ならば 1 です。{{判定式}}の構文に誤りがある場合その他のエラーが発生したときは、終了ステータスは 2 です。 [[notes]] == 補足 複雑な判定式は誤って解釈されることがあるので避けることをお勧めします。例えば +[ 1 -eq 1 -a -t = 1 -a ! foo ]+ は +[ 1 -eq 1 ] && [ -t = 1 ] && ! [ foo ]+ のようにコマンドを分けると式がより明確になります。 POSIX は、エラーが発生した場合の終了ステータスを ``2 以上'' と定めています。また POSIX には以下の演算子の規定はありません: +-G+, +-k+, +-N+, +-O+, +-nt+, +-ot+, +-ef+, +==+, +===+, +!==+, +<+, +<=+, +>+, +>=+, +=~+, +-veq+, +-vne+, +-vgt+, +-vge+, +-vlt+, ++-vle++。 POSIX に +-o+ の単項演算子としての規定はありません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_times.txt000066400000000000000000000020771354143602500156050ustar00rootroot00000000000000= Times 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Times 組込みコマンド dfn:[Times 組込みコマンド]はシェルとシェルが起動したコマンドが消費した CPU 時間を表示します。 [[syntax]] == 構文 - +times+ [[description]] == 説明 Times コマンドはシェルプロセスとその子プロセスが消費した CPU 時間を標準出力に出力します。一行目にシェルプロセス自身がユーザモードおよびシステムモードで消費した CPU 時間をそれぞれ表示します。二行目にシェルの全ての子孫プロセス (親プロセスが wait していないものを除く) がユーザモードおよびシステムモードで消費した CPU 時間をそれぞれ表示します。 [[exitstatus]] == 終了ステータス エラーがない限り times コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Times コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_trap.txt000066400000000000000000000122731354143602500154310ustar00rootroot00000000000000= Trap 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Trap 組込みコマンド dfn:[Trap 組込みコマンド]はシェルがシグナルを受信したときの動作を設定します。 [[syntax]] == 構文 - +trap+ - +trap {{動作}} {{シグナル}}...+ - +trap {{シグナル番号}} [{{シグナル}}...]+ - +trap -p [{{シグナル}}...]+ [[description]] == 説明 Trap コマンドはシェルプロセスがシグナルを受信したときの動作 (dfn:[トラップ]) を表示または設定します。 オペランドに{{動作}}と{{シグナル}}を指定して trap コマンドを実行すると、シェルが{{シグナル}}を受信した際に指定した{{動作}}を行うようになります。最初のオペランドが{{シグナル番号}}の場合、それと残りの{{シグナル}}に対する動作は、動作として +-+ が指定されたときと同様に標準の動作に設定されます。 +-p+ (+--print+) オプションを指定した場合またはオペランドを一つも指定していない場合は、trap コマンドは現在のトラップの設定状況をコマンドとして解釈可能な形式で標準出力に出力します。{{シグナル}}が与えられているときはそのシグナルに関する設定を、与えられていないときは全ての設定を出力します。ただし、特定の状況では trap コマンドは現在の設定ではなく以前の設定を表示します (下記補足参照)。 [[options]] == オプション +-p+:: +--print+:: 現在のトラップの設定を表示します。 [[operands]] == オペランド {{動作}}:: シグナルを受信した際の動作を指定します。{{動作}}がハイフン一つ (+-+) ならば、シェルはシステムで規定された標準の動作を行います。{{動作}}が空文字列ならば、シェルはシグナルを無視します。それ以外の値を指定すると、シェルはこのオペランドをコマンドとみなして、シグナル受信時にこれを解釈・実行します (コマンドの実行中にシグナルを受信したときは、コマンドが終了した後にトラップを実行します)。 {{シグナル}}:: 動作の対象となるシグナルです。シグナルはシグナル番号とシグナル名のどちらかで指定します。 + {{シグナル}}として +0+ または +EXIT+ を指定すると、これはシェルの終了時に発生する仮想のシグナルを指定しているとみなします。この仮想のシグナルに対して設定された{{動作}}は、シェルが正常終了する直前に実行されます。 {{シグナル番号}}:: {{シグナル}}と同様ですが、シグナルを番号で指定します。 [[exitstatus]] == 終了ステータス トラップが正しく設定または表示されたときは終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 Trap コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX には +-p+ (+--print+) オプションに関する規定はありません。よってこのオプションは link:posix.html[POSIX 準拠モード]では使えません。 POSIX は、シグナル名は +INT+ や +QUIT+ のように最初の SIG を除いた形で指定しなければならないと規定しています。Yash では、拡張として SIG を付けた形でも指定できますし、シグナル名の大文字と小文字を区別しません (このような拡張は POSIX でも認められています)。 === 出力の再利用 Trap コマンドが出力したトラップの設定を変数に保存しておき、後で link:_eval.html[eval コマンド]でそれを実行することで元のトラップの設定を復活させることができます。 ---- saved_trap=$(trap) trap '...' INT eval "$saved_trap" ---- ただし、このテクニックの挙動には裏があります。Trap コマンドの出力を変数に保存するために{zwsp}link:expand.html#cmdsub[コマンド置換]を使用しますが、コマンド置換は{zwsp}link:exec.html#subshell[サブシェル]で実行されます。サブシェルの開始時にトラップの設定は解除されるため、本来ならばサブシェル内の trap コマンドは何も出力せず、結果として変数にはトラップが保存されないことになります。 この問題を回避するため、POSIX は以下のうちどちらかの挙動を執ることをシェルに求めています。 - コマンド置換の中身がただ一つの trap コマンドである場合は、そのコマンド置換を実行するサブシェルの開始時にトラップ設定を解除しない。 - サブシェルの開始時にトラップ設定を解除する際に、以前の設定を憶えておく。サブシェル内で trap コマンドがトラップ設定を出力する際、まだそのサブシェル内で別の trap コマンドがトラップ設定を変更していなければ、憶えておいた以前の設定を出力する。 Yash は二つ目の選択肢に従います。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_true.txt000066400000000000000000000015401354143602500154350ustar00rootroot00000000000000= True 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - True 組込みコマンド dfn:[True 組込みコマンド]は何も行わずに 0 の終了ステータスで終了します。 [[syntax]] == 構文 - +true+ [[description]] == 説明 True コマンドは何も行いません。コマンドライン引数は一切無視します。 [[exitstatus]] == 終了ステータス True コマンドの終了ステータスは 0 です。 [[notes]] == 補足 True コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 link:_colon.html[コロンコマンド]は true コマンドと同様に何も行いませんが、true コマンドは準特殊組込みコマンドであるのに対しコロンコマンドは特殊組込みコマンドです。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_type.txt000066400000000000000000000021761354143602500154450ustar00rootroot00000000000000= Type 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Type 組込みコマンド dfn:[Type 組込みコマンド]はコマンドの種類を特定します。 [[syntax]] == 構文 - +type [-abefkp] [{{コマンド}}...]+ [[description]] == 説明 Type コマンドは link:_command.html[command コマンド]に +-V+ オプションを付けたものと同じです。その他オプション・オペランド・終了ステータスは command コマンドと同じです。 [[notes]] == 補足 POSIX では、type コマンドと command コマンドとの関係について規定していません。従って他のシェルの type コマンドは command コマンドに +-V+ オプションを付けたものとは異なる動作をすることがあります。また POSIX は type コマンドのオプションを規定していません。 link:posix.html[POSIX 準拠モード]では少なくとも一つ{{コマンド}}を指定する必要があります。 Type コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_typeset.txt000066400000000000000000000111241354143602500161520ustar00rootroot00000000000000= Typeset 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Typeset 組込みコマンド dfn:[Typeset 組込みコマンド]は{zwsp}link:params.html#variables[変数]または{zwsp}link:exec.html#function[関数]を表示・設定します。 [[syntax]] == 構文 - +typeset [-gprxX] [{{変数}}[={{値}}]...]+ - +typeset -f[pr] [{{関数}}...]+ [[description]] == 説明 +-f+ (+--functions+) オプションを付けずに実行すると、typeset コマンドは{zwsp}link:params.html#variables[変数]を出力または設定します。+-f+ (+--functions+) オプションを付けて実行すると、typeset コマンドは{zwsp}link:exec.html#function[関数]を出力または設定します。 +-p+ (+--print+) オプションを付けて実行すると、typeset コマンドはオペランドで指定した変数または関数を標準出力に出力します。+-p+ (+--print+) オプションを付けずに実行すると、typeset コマンドはオペランドで指定した変数または関数を設定します。オペランドを一つも与えずに実行すると、+-p+ (+--print+) オプションの有無にかかわらず typeset コマンドは全ての変数または関数を出力します (このとき +-g+ (+--global+) オプションがあれば本当にすべての変数を、そうでないときはローカル変数だけを出力します)。 [[options]] == オプション +-f+:: +--functions+:: 変数ではなく関数を表示または設定します。 +-g+:: +--global+:: このオプションを指定すると、新しく変数を作成する場合その変数をグローバル変数とします。すなわち変数は関数の実行が終わっても残ります。このオプションを指定しない場合、設定する変数は{zwsp}link:exec.html#localvar[ローカル変数]になります。 + オペランドがない場合は、このオプションを指定していると全ての変数を出力します。このオプションを指定していないとローカル変数だけ出力します。 +-p+:: +--print+:: 変数または関数の定義を (コマンドとして解釈可能な形式で) 出力します。 +-r+:: +--readonly+:: 設定する変数・関数をdfn:[読み取り専用]にします。読み取り専用の変数・関数は、値を変更したり削除したりできなくなります。 + 変数・関数を出力する際は、読み取り専用の変数・関数だけ出力します。 +-x+:: +--export+:: 設定する変数を{zwsp}link:params.html#variables[エクスポート対象]にします。 + 変数を出力する際は、エクスポート対象の変数だけ出力します。 +-X+:: +--unexport+:: 設定する変数をエクスポート対象から外します。 [[operands]] == オペランド {{変数}}:: 出力または設定する変数の名前です。 + 変数が既に存在する場合、変数の値は変わりません。存在しない変数を指定した場合、変数は存在するがその値は存在しない状態になります (このような変数は{zwsp}link:expand.html#params[パラメータ展開]では存在しない変数として扱います)。 {{変数}}={{値}}:: 設定する変数の名前とその値です。 + 指定した{{値}}が変数の値として設定されます。この形式のオペランドでは、+-p+ (+--print+) オプションを指定したときでもこの変数は出力ではなく設定されます。 {{関数}}:: 出力または設定する関数の名前です。存在しない関数を指定することはできません。 [[exitstatus]] == 終了ステータス エラーがない限り typeset コマンドの終了ステータスは 0 です。 [[notes]] == 補足 既にローカル変数が存在する場合、それを無視してグローバル変数を新しく作ることはできません (+-g+ (+--global+) オプションを指定していても既存のローカル変数が再設定されます)。 Typeset コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX では typeset コマンドの動作は規定されていません。 link:_export.html[Export コマンド]は typeset コマンドに +-gx+ オプションを付けたものと同じです。{zwsp}link:_readonly.html[Readonly コマンド]は typeset コマンドに +-gr+ オプションを付けたものと同じです。{zwsp}link:_local.html[Local コマンド]は typeset と同じですが +-f+ (+--functions+) および +-g+ (+--global+) オプションは使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_ulimit.txt000066400000000000000000000115401354143602500157620ustar00rootroot00000000000000= Ulimit 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Ulimit 組込みコマンド dfn:[Ulimit 組込みコマンド]はシェルプロセスのリソース制限を表示・設定します。 [[syntax]] == 構文 - +ulimit -a [-H|-S]+ - +ulimit [-H|-S] [-efilnqrstuvx] [{{値}}]+ [[description]] == 説明 Ulimit コマンドはシェルプロセスのリソース制限値を表示または設定します。 +-a+ (+--all+) オプションを付けて実行すると、ulimit コマンドは全ての種類のリソースについて現在の制限値を一覧形式で標準出力に出力します。+-a+ (+--all+) オプションを付けないで実行すると、一種類のリソースについて制限値を表示または設定します。オペランドで{{値}}を指定している場合、その値に制限値を設定します。{{値}}を指定していない場合は現在の制限値を標準出力に出力します。表示・設定するリソースの種類はオプションで指定します。設定したリソースの制限値はシェルが起動する各コマンドに受け継がれます。 リソースの各種類について、dfn:[ハードリミット]とdfn:[ソフトリミット]の二種類の制限値があります。ソフトリミットがハードリミットを超えることはできません。またハードリミットを緩めるには専用の権限が必要です。 +-H+ (+--hard+) オプションを指定している場合、ulimit コマンドはハードリミットを表示または設定します。+-S+ (+--soft+) オプションを指定している場合、ulimit コマンドはソフトリミットを表示または設定します。どちらのオプションも指定してない場合、ulimit コマンドはソフトリミットのみを表示するかまたはハードリミットとソフトリミットの両方を設定します。 [[options]] == オプション +-H+:: +--hard+:: ハードリミットを表示または設定します。 +-S+:: +--soft+:: ソフトリミットを表示または設定します。 +-a+:: +--all+:: 全種類のリソース制限値を表示します。 以下のオプションは表示・設定するリソースの種類を指定します。これらのオプションがどれも与えられていないときは、+-f+ が与えられたものとみなします。(表示・設定可能なリソースの種類はシステムによって異なります) +-c+:: +--core+:: プロセスが強制終了させられたときにできるコアファイルのサイズの限界 (512 バイト単位) +-d+:: +--data+:: プロセスが使用できるデータセグメント領域のサイズの限界 (キロバイト単位) +-e+:: +--nice+:: スケジューリング優先度 (nice 値) の限界 +-f+:: +--fsize+:: プロセスが作成できるファイルのサイズの限界 (512 バイト単位) +-i+:: +--sigpending+:: プロセスの処理待ちシグナルの個数の限界 +-l+:: +--memlock+:: RAM 上にロック可能なメモリサイズの限界 (キロバイト単位) +-m+:: +--rss+:: プロセスの lang:en[resident set] (RAM 上に存在する仮想ページ) の数の限界 (キロバイト単位) +-n+:: +--nofile+:: プロセスが使用できるファイル記述子の最大値 + 1 +-q+:: +--msgqueue+:: POSIX メッセージキューのサイズの限界 +-r+:: +--rtprio+:: リアルタイムスケジューリングの優先度の限界 +-s+:: +--stack+:: プロセスが使用できるスタック領域のサイズの限界 (キロバイト単位) +-t+:: +--cpu+:: プロセスが使用できる CPU 時間の限界 (秒単位) +-u+:: +--nproc+:: プロセスが起動できる子プロセスの個数の限界 +-v+:: +--as+:: プロセスが使用できるメモリ領域全体のサイズの限界 (キロバイト単位) +-x+:: +--locks+:: プロセスがロックできるファイルの個数の限界 [[operands]] == オペランド {{値}}:: 設定するリソース制限値です。 + 値は基本的に 0 以上の自然数で指定しますが、自然数の代わりに ++hard++、++soft++、++unlimited++ のいずれかの文字列を指定することもできます。これらの文字列はそれぞれ現在のハードリミットの値、現在のソフトリミットの値、無制限を表します。 [[exitstatus]] == 終了ステータス リソース制限値が正しく出力または設定できたときは、終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 POSIX が規定しているオプションは +-f+ だけです。また POSIX はオペランドの{{値}}として自然数しか規定していません。 Ulimit コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_umask.txt000066400000000000000000000077651354143602500156150ustar00rootroot00000000000000= Umask 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Umask 組込みコマンド dfn:[Umask 組込みコマンド]はシェルプロセスのファイルモード作成マスクを表示・設定します。 [[syntax]] == 構文 - +umask {{マスク}}+ - +umask [-S]+ [[description]] == 説明 オペランドが与えられていないとき、umask コマンドはシェルプロセスの現在のファイルモード作成マスクを標準出力に出力します。+-S+ (+--symbolic+) オプションで出力の形式を指定できます。出力はオペランドとして再利用可能な形式になっています。 オペランドが与えられているとき、umask コマンドはシェルプロセスのファイルモード作成マスクを与えられたオペランドの値に設定します。 [[options]] == オプション +-S+:: +--symbolic+:: このオプションが指定されているときは、記号形式でファイルモード作成マスクを出力します。このオプションが指定されていないときは、数値形式で出力します。 [[operands]] == オペランド {{マスク}}:: このオペランドが与えられているときは、umask コマンドはファイルモード作成マスクをこの値に設定します。値は以下に述べる数値形式と記号形式のどちらかで与えます。 === 数値形式 数値形式は 0 以上の八進整数でマスクを指定します。これは以下の各モードを表す自然数のいくつかの和です。 0400:: ユーザの読み取り権限 0200:: ユーザの書き込み権限 0100:: ユーザの実行権限 0040:: グループの読み取り権限 0020:: グループの書き込み権限 0010:: グループの実行権限 0004:: その他の読み取り権限 0002:: その他の書き込み権限 0001:: その他の実行権限 === 記号形式 記号形式は以下のような書式の記号列でマスク**しない**権限を指定します。 記号列は一つ以上の{{節}}をカンマ (+,+) で区切ったものです。 {{節}}は{{対象}}の列と一つ以上の{{動作}}からなります。 {{対象}}には以下の記号をいくつでも指定できます。 +u+:: ユーザ +g+:: グループ +o+:: その他 +a+:: ユーザ・グループ・その他の全て {{対象}}が一つも指定されていない場合は +a+ が指定されているものとみなします。 {{動作}}は以下の{{演算子}}と{{権限}}の組み合わせです。 {{演算子}}は以下のどれかです。 +=+:: {{対象}}に対する設定を{{権限}}にします。 +++:: {{対象}}に対する設定に{{権限}}を加えます。 +-+:: {{対象}}に対する設定から{{権限}}を除きます。 {{権限}}は以下のどれかです。 +r+:: 読み取り権限 +w+:: 書き込み権限 +x+:: 実行権限 +X+:: 実行権限 (元々実行権限が設定されていた場合のみ) +s+:: Set-user-ID, Set-group-ID +u+:: 現在のユーザの値 +g+:: 現在のグループの値 +o+:: 現在のその他の値 +r+, +w+, +x+, +X+, +s+ の記号同士は一度に組み合わせて指定できます。 例えば +umask u=rwx,go+r-w+ を実行すると、ユーザの読み取り権限と書き込み権限と実行権限をマスクしないようにし、グループとその他の読み取り権限をマスクしないようにし、グループとその他の書き込み権限をマスクするようにします。 [[exitstatus]] == 終了ステータス ファイルモード作成マスクが正しく出力または設定できたときは、終了ステータスは 0 です。エラーがあると終了ステータスは非 0 です。 [[notes]] == 補足 Ulimit コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 POSIX は +-S+ オプションを指定しなかった場合の出力形式が必ずしも数値形式であるとは規定していません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_unalias.txt000066400000000000000000000020211354143602500161050ustar00rootroot00000000000000= Unalias 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Unalias 組込みコマンド dfn:[Unalias 組込みコマンド]は{zwsp}link:syntax.html#aliases[エイリアス]を削除します。 [[syntax]] == 構文 - +unalias {{エイリアス名}}...+ - +unalias -a+ [[description]] == 説明 Unalias コマンドはオペランドで指定した{zwsp}link:syntax.html#aliases[エイリアス]を削除します。 [[options]] == オプション +-a+:: +--all+:: 全てのエイリアスを削除します。 [[operands]] == オペランド {{エイリアス名}}:: 削除するエイリアスの名前です。 [[exitstatus]] == 終了ステータス エラーがない限り unalias コマンドの終了ステータスは 0 です。存在しないエイリアスをオペランドで指定した場合はエラーになります。 [[notes]] == 補足 Unalias コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_unset.txt000066400000000000000000000032201354143602500156110ustar00rootroot00000000000000= Unset 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Unset 組込みコマンド dfn:[Unset 組込みコマンド]は変数または関数を削除します。 [[syntax]] == 構文 - +unset [-fv] [{{名前}}...]+ [[description]] == 説明 Unset コマンドはオペランドで指定した名前の{zwsp}link:params.html#variables[変数]または{zwsp}link:exec.html#function[関数]を削除します。 元々存在しない変数・関数を削除しようとしても、何も起こりません (エラーにはなりません)。 [[options]] == オプション +-f+:: +--functions+:: 関数を削除します。 +-v+:: +--variables+:: 変数を削除します。 +-f+ (+--functions+) オプションと +-v+ (+--variables+) オプションの両方を指定した場合、後に指定したほうを優先します。どちらも指定していない場合は、+-v+ を指定したものとみなします。 [[operands]] == オペランド {{名前}}:: 削除する変数または関数の名前です。 [[exitstatus]] == 終了ステータス エラーがない限り unset コマンドの終了ステータスは 0 です。 [[notes]] == 補足 Unset コマンドは{zwsp}link:builtin.html#types[特殊組込みコマンド]です。 POSIX では、+-f+ と +-v+ のどちらのオプションも指定されていない場合、指定した名前の変数がない場合はかわりにその名前の関数を削除してよいと規定しています。 link:posix.html[POSIX 準拠モード]では少なくとも一つ{{名前}}を指定する必要があります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/_wait.txt000066400000000000000000000057631354143602500154350ustar00rootroot00000000000000= Wait 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Wait 組込みコマンド dfn:[Wait 組込みコマンド]はバックグラウンドの{zwsp}link:job.html[ジョブ]が終了するのを待ちます。 [[syntax]] == 構文 - +wait [{{ジョブ}}...]+ [[description]] == 説明 Wait コマンドは実行中のバックグラウンドジョブが終了するのを待ちます。{zwsp}link:job.html[ジョブ制御]が有効な時は、ジョブが停止したときも終了したとみなします。 Wait コマンドはジョブ制御が有効でないときでも{zwsp}link:syntax.html#async[非同期コマンド]の終了を待つのに使えます。 Wait コマンドの実行中にシェルがシグナルを受信した場合、そのシグナルに対し{zwsp}link:_trap.html[トラップ]が設定してあればそのトラップを直ちに実行し wait コマンドはそのまま終了します。またジョブ制御が有効な場合、シェルが SIGINT シグナルを受信すると wait コマンドは中断されます。 シェルが{zwsp}link:interact.html[対話モード]で、{zwsp}link:job.html[ジョブ制御]が有効で、非 link:posix.html[POSIX 準拠モード]のとき、ジョブが終了または停止した時にジョブの状態を出力します。 [[operands]] == オペランド {{ジョブ}}:: 終了を待つジョブ・非同期コマンドの{zwsp}link:job.html#jobid[ジョブ ID] またはプロセス ID です。 {{ジョブ}}を何も指定しないとシェルが有する全てのジョブ・非同期コマンドの終了を待ちます。 存在しないジョブ・非同期コマンドを指定すると、終了ステータス 127 で既に終了したジョブを指定したものとみなし、エラーにはしません。 [[exitstatus]] == 終了ステータス {{ジョブ}}が一つも与えられておらず、シェルが全てのジョブ・非同期コマンドの終了を正しく待つことができた場合、終了ステータスは 0 です。{{ジョブ}}が一つ以上与えられているときは、最後の{{ジョブ}}の終了ステータスが wait コマンドの終了ステータスになります。 Wait コマンドがシグナルによって中断された場合、終了ステータスはそのシグナルを表す 128 以上の整数です。その他の理由で wait コマンドがジョブの終了を正しく待つことができなかった場合、終了ステータスは 1 以上 126 以下です。 [[notes]] == 補足 Wait コマンドは{zwsp}link:builtin.html#types[準特殊組込みコマンド]です。 非同期コマンドのプロセス ID は非同期コマンドを実行した直後に{zwsp}link:params.html#special[特殊パラメータ +!+] の値を見ることで知ることができます。ジョブ制御が有効なときは link:_jobs.html[jobs コマンド]でプロセス ID を調べることもできます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/asciidoc.conf000066400000000000000000000016061354143602500162060ustar00rootroot00000000000000[macros] (?su)dfn:\[(?P.*?)\]=dfn (?su)dfn:(?P\w+)\[(?P.*?)\]=dfnid (?su)lang:(?P\w+)\[(?P.*?)\]=lang [quotes] ~= ((|))= (((|)))= +=code ++=#code `=#code {{|}}=#var ifdef::basebackend-html[] [tags] code=| var=| [dfn-inlinemacro] {value} [dfnid-inlinemacro] {value} ifdef::backend-xhtml11[] [lang-inlinemacro] {value} endif::backend-xhtml11[] ifndef::backend-xhtml11[] [lang-inlinemacro] {value} endif::backend-xhtml11[] endif::basebackend-html[] ifdef::basebackend-docbook[] [tags] code=| var=| [dfn-inlinemacro] {value} [dfnid-inlinemacro] {value} [lang-inlinemacro] {value} endif::basebackend-docbook[] # vim: set filetype=cfg: yash-2.49/doc/ja/asciidoc.css000066400000000000000000000034261354143602500160530ustar00rootroot00000000000000html, body { color: black; background: white; } body { margin: 1em 5%; text-align: justify; } h1, h2, h3, h4, h5, h6 { padding: 0; padding-left: 0.5em; border-style: none none solid solid; border-bottom-width: 1px; } h1, h2, h3 { margin: 1em -3.5% 0.5em; } h4, h5, h6 { margin: 1em -2.5% 0.5em; } h1 { border-color: orange; border-left-width: 1em; } h2 { border-color: navy; border-left-width: 0.7em; } h3 { border-color: green; border-left-width: 0.5em; } .sectionbody { /*margin: 0 3%;*/ } #footer { margin: 1em -3% 0; font-size: small; border: none; border-top: 2px solid silver; } .title { border-bottom: 1px solid maroon; font-weight: bold; } .list-group p { margin-top: 0.2em; margin-bottom: 0.2em; } .admonitionblock td.icon { vertical-align: top; padding-right: 0.5em; } .exampleblock { margin: 0.8em auto; border: 0.15em dotted maroon; padding: 0 0.5em; } .exampleblock > .title { margin-top: 0.5em; } ul, ol { list-style-position: outside; } ol.arabic { list-style-type: decimal; } ol.loweralpha { list-style-type: lower-alpha; } ol.upperalpha { list-style-type: upper-alpha; } ol.lowerroman { list-style-type: lower-roman; } ol.upperroman { list-style-type: upper-roman; } li p { margin-top: 0.65em; margin-bottom: 0.65em; } dd p { margin-top: 0.1em; margin-bottom: 0.65em; } pre { border: dashed 1px gray; padding: 0.3em; } code, kbd, samp { border: dotted 1px gray; padding: 1px; text-decoration: none; white-space: pre-wrap; } pre > code:only-child, pre > kbd:only-child, pre > samp:only-child { display: block; } pre code, pre kbd, pre samp { white-space: inherit; } code { background: #efe; } kbd { background: #ffd; } samp { background: #eef; } var { color: maroon; background: inherit; } dfn { font-style: normal; font-weight: bold; } yash-2.49/doc/ja/builtin.txt000066400000000000000000000171321354143602500157710ustar00rootroot00000000000000= 組込みコマンド :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - 組込みコマンド :description: Yash の組込みコマンドに関する共通の事柄について dfn:[組込みコマンド]とはシェルに内蔵されたコマンドです。組込みコマンドは外部のプログラムを起動することなくシェル自身によって実行されます。 ifdef::basebackend-html[] 組込みコマンドの一覧は{zwsp}link:index.html#builtins[目次]を参照してください。 endif::basebackend-html[] [[types]] == 組込みコマンドの種類 Yash の組込みコマンドは特殊組込みコマンド・準特殊組込みコマンド・通常の組込みコマンドの三種類に分けられます。 dfn:[特殊組込みコマンド]は他の組込みコマンドよりも特に重要なコマンドで、他のコマンドとは異なる性質をいくつか持っています。まず、特殊組込みコマンドは対応する外部コマンドの存在に関係なく常に実行されます。また特殊組込みコマンドにおける変数代入の結果はコマンドの実行が終わった後も残ります。さらに link:posix.html[POSIX 準拠モード]では、特殊組込みコマンドにおいてリダイレクトエラーまたは代入エラーが発生した場合あるいはコマンドのオプションやオペランドの使い方が間違っている場合、シェルが{zwsp}link:interact.html[対話モード]でなければシェルは直ちに非 0 の終了ステータスで終了します。 dfn:[準特殊組込みコマンド]は特殊組込みコマンドに次いで重要な組込みコマンドで、対応する外部コマンドの存在に関係なく常に実行されます。その他の点では準特殊組込みコマンドは通常の組込みコマンドと同じです。 外部コマンドとして実装可能な組込みコマンドや POSIX に規定されていない組込みコマンドを含む、重要度の低い組込みコマンドはdfn:[通常の組込みコマンド]です。POSIX 準拠モードでは、通常の組込みコマンドは対応する外部コマンドが link:exec.html#search[+PATH+ 変数の検索]で見つかった場合のみ実行されます。 [[argsyntax]] == コマンドの引数の構文 ここでは組込みコマンドの引数に関する一般的な規則について説明します。Yash の組込みコマンドの引数の指定・解釈の仕方は、他に断りがない限りこの規則に従います。 コマンドの引数は、オプションとオペランドの二種類に分けられます。dfn:[オプション]はハイフン (+-+) で始まる引数で、主にコマンドの動作を変更するのに使われます。オプションの中にはそれに対応する引数をとるものもあります。dfn:[オペランド]はオプション以外の引数で、主にコマンドが処理を行う対象を指定するのに使われます。 一つのコマンドに複数のオプションを与える場合、原則としてそれらのオプションの順序はコマンドの動作に影響しません。しかしオペランドの順序には意味があります。 オプションには一文字のオプションと長いオプションとがあります。dfn:[一文字のオプション]は英数字一文字によって識別されるオプションです。dfn:[長いオプション]はもっと長い文字列によって識別されるオプションです。POSIX 規格は一文字のオプションについてしか規定していないため、{zwsp}link:posix.html[POSIX 準拠モード]では長いオプションは使えません。 一文字のオプションは、一つのハイフンと一文字の英数字からなります。例えば +-a+ は一文字のオプションです。引数をとるオプションでは、コマンドに与えられた引数の並びの中でそのオプションの直後に位置している引数がそのオプションに対する引数とみなされます。 .Set 組込みコマンドと一文字のオプション ==== Set 組込みコマンドにおいて、+-m+ は引数をとらない一文字のオプション、+-o+ は引数をとる一文字のオプションです。 - +set -o errexit -m+ - +set -oerrexit -m+ この二つの例では、+errexit+ が +-o+ オプションに対する引数となります。 ==== 上の二つ目の例では、+-o+ オプションとそれに対する引数が一つのコマンドライン引数になっています。POSIX はこのような書き方は避けなければならないと定めており、POSIX に従うアプリケーションは必ず一つ目の例のようにオプションとそれに対する引数を別々のコマンドライン引数として与えなければなりません。しかし yash はどちらの指定の仕方も受け付けるようになっています。 引数をとらない複数の一文字のオプションは、一つにまとめて書くことができます。例えば +-a+, +-b+, +-c+ という三つのオプションは +-abc+ と書けます。 長いオプションは、二つのハイフンとオプション名を表す文字列からなります。例えば +--long-option+ は長いオプションです。オプション名は他と紛らわしくない限り末尾を省略できます。例えば他に +--long+ で始まる長いオプションがなければ、+--long-option+ は +--long+ と省略できます。引数をとるオプションでは、一文字のオプションの場合と同様に、オプションの直後にあるコマンドライン引数がそのオプションに対する引数とみなされます。あるいは、オプション名の後に等号 (+=+) で区切って直接引数を与えることもできます。 .Fc 組込みコマンドと長いオプション ==== Fc 組込みコマンドにおいて、+--quiet+ は引数をとらない長いオプション、+--editor+ は引数をとる長いオプションです。 - +fc --editor vi --quiet+ - +fc --editor=vi --quiet+ この二つの例では、+vi+ が +--editor+ オプションに対する引数となります。 ==== オプション (およびオプションに対する引数) 以外の引数は、全てオペランドとみなされます。POSIX は、オペランドは全てオプションより後に書かなければならないと定めています。そのため link:posix.html[POSIX 準拠モード]では、最初のオペランドより後にある引数は (たとえそれがオプションであるように見えても) 全てオペランドとして解釈します。POSIX 準拠モードでないときは、オペランドの後にオプションを書いても構いません。 POSIX 準拠モードであるかどうかにかかわらず、ハイフン二つからなる引数 (+--+) はオプションとオペランドとの区切りとして使えます。この区切り以降の全てのコマンドライン引数はオペランドとして解釈されるため、ハイフンで始まるオペランドを正しく指定できます。 .Set 組込みコマンドのオプションとオペランド ==== - `set -a -b -- -c -d` この例では、+-a+ と +-b+ がオプションで、+-c+ と +-d+ がオペランドとなります。区切り (+--+) 自体はオプションでもオペランドでもありません。 ==== POSIX 準拠モードであるかどうかにかかわらず、ハイフン一つからなる引数 (+-+) は常にオペランドとみなされます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/exec.txt000066400000000000000000000345271354143602500152560ustar00rootroot00000000000000= コマンドの実行とその環境 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - コマンドの実行とその環境 :description: このページではコマンドがどのように実行されるかを説明します。 この節ではコマンドがどのように実行されるかを説明します。 [[simple]] == 単純コマンドの実行 link:syntax.html#simple[単純コマンド]は以下の手順に従って実行されます。 . 単純コマンドに含まれる、変数代入とリダイレクト以外のトークンを全て{zwsp}link:expand.html[展開]します。展開エラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。 + 以下、展開の結果得られた最初の単語をdfn:[コマンド名]、それ以外の単語をdfn:[コマンド引数]と呼びます。得られた単語が一つの場合は、コマンド引数は存在しません。得られた単語が一つもない場合は、コマンド名もコマンド引数も存在しません。 . 単純コマンドに対する{zwsp}link:redir.html[リダイレクト]を実行します。リダイレクトに含まれるトークンの展開はここで行われます。リダイレクトエラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。リダイレクトに含まれるトークンの展開時のエラーはリダイレクトエラーに含まれます。 . 単純コマンドに含まれる変数代入を実行します (配列の代入を含む)。それぞれの変数代入について、値が展開され、指定された名前の{zwsp}link:params.html#variables[変数]に代入されます。代入エラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。代入される値の展開時のエラーは代入エラーに含まれます。 + -- - コマンド名が存在しないか、あるいはコマンド名が{zwsp}link:builtin.html#types[特殊組込み]を示している場合は、変数代入は恒久的です。すなわち、代入の結果はこの単純コマンドの実行が終わった後もそのまま残ります。 - それ以外の場合は、変数代入は一時的です。すなわち、代入の効果はこの単純コマンドの実行中のみ有効で、実行が終わった後に代入は取り消されます。 -- + コマンド名が指定された場合または link:_set.html#so-allexport[all-export オプション]が有効な場合は、代入される変数は自動的に{zwsp}link:params.html#variables[エクスポート]対象になります。 + [NOTE] Yash 以外のシェルでは代入の動作が異なることがあります。特殊組込みまたは関数では変数はエクスポート対象にならないかもしれません。また関数の実行終了後も変数が残るかもしれません。 . コマンド名が存在しない場合は、単純コマンドの実行はこれで終わりです。単純コマンドの終了ステータスは 0 になります (ただし単純コマンド実行中に{zwsp}link:expand.html#cmdsub[コマンド置換]が行われた時は、最後のコマンド置換のコマンドの終了ステータスが単純コマンドの終了ステータスになります)。 . 後述の<>の仕方に従って実行すべきコマンドを特定し、そのコマンドを実行します。 + -- - コマンドが外部コマンドの場合は、コマンドは<>で exec システムコールを呼び出すことにより実行されます。コマンド名とコマンド引数が起動するコマンドに渡されます。またエクスポート対象となっている変数が環境変数としてコマンドに渡されます。 - コマンドが{zwsp}link:builtin.html[組込みコマンド]の場合は、コマンド引数を引数として組込みコマンドが実行されます。 - コマンドが<>の場合は、その関数の内容が実行されます。コマンド引数が関数の引数として渡されます。 実行したコマンドの終了ステータスがこの単純コマンドの終了ステータスになります。コマンドが見つからなかった場合は、コマンドは実行されず終了ステータスは 127 になります。コマンドが見つかったが起動に失敗した場合は、終了ステータスは 126 になります。コマンドが起動されたがシグナルによって中断された場合は、終了ステータスはそのシグナルの番号に 384 を足した数になります。 [NOTE] POSIX ではコマンドがシグナルによって中断された場合の終了ステータスは 128 より大きな数としか定められていないので、yash 以外のシェルでは終了ステータスが異なることがあります。 非 link:posix.html[POSIX 準拠モード]においてコマンドが見つからなかったとき、コマンド ifdef::basebackend-html[] pass:[eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"`] が実行されます。ただしこのとき{zwsp}link:params.html#positional[位置パラメータ]はコマンド名とコマンド引数に一時的に置き換えられます。またこのコマンドの実行中に定義された<>はこのコマンドの終了時に削除されます。このコマンドの実行時には link:params.html#sv-handled[++HANDLED++ ローカル変数]が空文字列を値としてあらかじめ定義されます。このコマンドの実行後にこの変数の値が空文字列でなくなっていれば、このコマンドの終了ステータスがこの単純コマンドの終了ステータスとなり、コマンドが見つからなかったことはエラーとはみなされません。 -- [[search]] === コマンドの検索 単純コマンドで実行すべきコマンドは、展開で得られたコマンド名に基づいて以下の手順で特定されます。 . コマンド名にスラッシュ (+/+) が含まれている場合は、それが実行すべき外部コマンドへのパス名であると特定されます。 . コマンド名が{zwsp}link:builtin.html#types[特殊組込みコマンド]ならば、その組込みコマンドが実行すべきコマンドとして特定されます。 . コマンド名と同じ名前の<>が存在すれば、その関数が実行すべきコマンドとして特定されます。 . コマンド名が{zwsp}link:builtin.html#types[準特殊組込みコマンド]ならば、その組込みコマンドが実行すべきコマンドとして特定されます。 . コマンド名が{zwsp}link:builtin.html#types[通常の組込みコマンド]ならば、その組込みコマンドが実行すべきコマンドとして特定されます。(link:posix.html[POSIX 準拠モード]のときを除く) . link:params.html#sv-path[+PATH+ 変数]の値に従って、実行すべき外部コマンドを検索しそのパス名を特定します。 + -- +PATH+ 変数の値は、いくつかのディレクトリのパス名をコロン (+:+) で区切ったものとみなされます (空のパス名はシェルの作業ディレクトリを表しているものとみなします)。それらの各ディレクトリについて順に、ディレクトリの中にコマンド名と同じ名前の実行可能な通常のファイルがあるか調査します。そのようなファイルがあれば、そのファイルが実行すべき外部コマンドとして特定されます (ただし、コマンド名と同じ名前の組込みコマンドがあれば、代わりにその組込みコマンドが実行すべきコマンドとして特定されます)。どのディレクトリにもそのようなファイルが見つからなければ、実行すべきコマンドは見つからなかったものとみなされます。 -- 外部コマンドの検索が成功しパス名が特定できた場合、そのパス名が絶対パスならば、シェルはそのパス名を記憶し、再び同じコマンドを実行する際に検索の手間を省きます。ただし、再びコマンドを実行しようとした際に、記憶しているパス名に実行可能ファイルが見当たらない場合は、検索をやり直します。シェルが記憶しているパス名は link:_hash.html[hash 組込みコマンド]で管理できます。 [[exit]] == シェルの終了 シェルは、入力が終わりに達して全てのコマンドを解釈・実行し終えた時や、{zwsp}link:_exit.html[exit 組込みコマンド]を実行したときなどに終了します。シェルの終了ステータスは、シェルが最後に実行したコマンドの終了ステータスを 256 で割った余りです (一つもコマンドを実行しなかったときは 0)。 link:_trap.html[Trap 組込みコマンド]でシェル終了時のハンドラが登録されている場合は、シェルが終了する直前にそのハンドラが実行されます。ただしこのハンドラ内で実行したコマンドはシェルの終了ステータスには影響しません。 link:interact.html[対話モード]でないシェルの実行中に下記エラーが発生した場合、シェルは直ちに終了します。このときシェルの終了ステータスは非 0 です。 - 文法エラーのためコマンドを解釈できないとき (link:invoke.html#init[シェルの初期化]中を除く) - link:posix.html[POSIX 準拠モード]で、{zwsp}link:builtin.html#types[特殊組込みコマンド]の実行中にエラーが発生したとき - POSIX 準拠モードで、特殊組込みコマンドに対して{zwsp}link:redir.html[リダイレクト]エラーが発生したとき - link:syntax.html#simple[単純コマンド]で代入エラーが発生したとき - link:expand.html[展開]エラーが発生したとき (シェルの初期化中を除く) [NOTE] Yash はそうではありませんが、<>において実行すべきコマンドが見つからなかったときに直ちに終了するようなシェルもあります。 [[function]] == 関数 dfn:[関数]は一つの複合コマンドを単純コマンドのように呼び出せるようにする機構です。関数は{zwsp}link:syntax.html#funcdef[関数定義コマンド]によって定義でき、{zwsp}link:syntax.html#simple[単純コマンド]によって実行できます。関数を削除するには link:_unset.html[unset 組込みコマンド]を使います。 Yash には、シェルの起動時に最初から定義されている関数は一つもありません。 関数の実行は、関数の内容である複合コマンドを実行することによって行われます。関数の実行中は、関数の引数が{zwsp}link:params.html#positional[位置パラメータ]になります。それまでの位置パラメータは一時的に使えなくなりますが関数の実行が終わった時に元の位置パラメータに戻ります。 [[localvar]] === ローカル変数 dfn:[ローカル変数]とは、関数の実行中にだけ有効な一時的な変数です。ローカル変数は link:_typeset.html[typeset 組込みコマンド]を使って作ることができます。また link:syntax.html#for[for ループ]の実行時に暗黙的に作られることもあります。関数の実行中に作られたローカル変数は関数の実行が終わった時に削除され、ローカル変数を作る前の元の変数の状態に戻ります。 関数内で定義したローカル変数は、関数の実行に先立って定義してあった同名の他の変数を__隠蔽__します。隠蔽された変数は、関数の実行が終わってローカル変数がなくなるまで使えなくなります。 関数の実行中でないときにローカル変数を作ることはできません。ローカル変数を作ろうとしても、通常の変数になります。 [[environment]] == コマンドの実行環境 シェルは実行時に以下の情報を保持します。 - 作業ディレクトリ - 開いているファイル記述子 - ファイル作成マスク (link:_umask.html[umask]) - 受信時の挙動が ``無視'' に設定されたシグナルの集合 (link:_trap.html[trap]) - link:params.html#variables[環境変数] - リソース制限 (link:_ulimit.html[ulimit]) これらの情報はシェルが起動されたときに元のプログラムからシェルに受け継がれます。そしてシェルが起動する外部コマンドやサブシェルにもシェルから受け継がれます。 これらの情報は所定の組込みコマンド等を使って変更可能です。 [[subshell]] === サブシェル dfn:[サブシェル]は、実行中のシェルのプロセスのコピーです。サブシェルは括弧 +( )+ で囲んだ{zwsp}link:syntax.html#grouping[グルーピング]や{zwsp}link:syntax.html#pipelines[パイプライン]で使われます。 サブシェルはシェルのプロセスのコピーであるため、上記の情報の他にシェルで定義された関数やエイリアスなどの情報も元のシェルから受け継ぎます。ただし、 - link:_trap.html[Trap 組込みコマンド]で登録したトラップの設定は、(受信時の挙動が ``無視'' のものを除き) サブシェルではすべて解除されます。(注) - サブシェルでは{zwsp}link:interact.html[対話モード]と{zwsp}link:job.html[ジョブ制御]は解除され、元のシェルのジョブの情報は受け継がれません。 サブシェルは元のシェルとは独立しているため、サブシェルでの作業ディレクトリの変更や変数代入は元のシェルに影響しません。 [NOTE] Yash 以外のシェルでは、サブシェル内で実行されるコマンドが trap 組込みコマンドのみの場合にはトラップの設定を解除しないものもあります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/expand.txt000066400000000000000000000734761354143602500156170ustar00rootroot00000000000000= 単語の展開 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - 単語の展開 :description: Yash がサポートする単語展開について コマンドを構成する各単語は、そのコマンドが実行されるときに展開されます。dfn:[展開]とは単語に含まれるパラメータやパターンを処理して具体的な文字列値に置き換えることです。展開には以下の七種類があります。 . <> . <> . <> . <> . <> . <> . <> これらの展開は上に挙げた順序で行われます。特に最初の四つ (チルダ展開・パラメータ展開・コマンド置換・数式展開) をdfn:[四種展開]といいます。 [[tilde]] == チルダ展開 dfn:[チルダ展開]は、+~+ で始まる単語を特定のパス名に置き換える展開です。単語の先頭にある +~+ から最初の +/+ まで (+/+ がない場合は単語全体) が指定されたパス名に変換されます。ただし、置き換えられる部分が一文字でも{zwsp}link:syntax.html#quotes[クォート]されている場合は、チルダ展開は行われません。 展開される内容は、置き換えられる部分の書式によって以下のように決まります。 +~+:: 単なる +~+ は、{zwsp}link:params.html#sv-home[+HOME+ 変数]の値に置き換えられます。 +~{{ユーザ名}}+:: +~+ の後にユーザ名が書かれている場合は、そのユーザのホームディレクトリのパス名に置き換えられます。 +~++:: +~++ は、{zwsp}link:params.html#sv-pwd[+PWD+ 変数]の値に置き換えられます。 +~-+:: +~-+ は、{zwsp}link:params.html#sv-oldpwd[+OLDPWD+ 変数]の値に置き換えられます。 +~+{{n}}+:: +~-{{n}}+:: この {{n}} は 0 以上の整数でなければなりません。この形式のチルダ展開は、+{{n}} または -{{n}} で指定されるディレクトリスタック内のパスの一つに展開されます。(link:_dirs.html[dirs 組込みコマンド]参照) 変数代入の値に対してチルダ展開が行われる際、値がコロンで区切ってある場合は、コロンで区切ってある各部分をそれぞれ単語とみなしてチルダ展開します。例えば +HOME+ 変数の値が +/home/foo+ のとき、 ---- VAR=~/a:~/b:~/c ---- は ---- VAR=/home/foo/a:/home/foo/b:/home/foo/c ---- と等価です。 チルダ展開に失敗した場合 (指定されたパス名が何らかの原因で得られなかった場合) の動作は POSIX では規定されていませんが、yash では何事もなかったかのように処理を続行します (置き換えられるはずだった部分はそのまま残され、エラーメッセージなどは出ません)。 link:posix.html[POSIX 準拠モード]では +~+ と +~{{ユーザ名}}+ の形式の展開のみが有効です。 [[params]] == パラメータ展開 dfn:[パラメータ展開]は、単語の一部をパラメータの値に置き換える展開です。 よく使われる単純なパラメータ展開の形式は +${{{パラメータ名}}}+ です。これは{{パラメータ名}}で指定されたパラメータの値に展開されます。さらに、以下の場合には{{パラメータ名}}を囲む括弧を省略して +${{パラメータ名}}+ のように書くこともできます。 - {{パラメータ名}}が{zwsp}link:params.html#special[特殊パラメータ]の場合 - {{パラメータ名}}が一桁の{zwsp}link:params.html#positional[位置パラメータ]の場合 - {{パラメータ名}}が変数名で、直後に変数名の一部として誤解される恐れのある文字がない場合。例えば +${path}-name+ という単語は +$path-name+ と書くこともできますが、 +${path}name+ を +$pathname+ と書くことはできません。 {{パラメータ名}}として特殊パラメータでも位置パラメータでも変数名でもないものを指定した場合は、構文エラーになります。(Yash 以外のシェルでは構文エラーではなく展開エラーになるものもあります) シェルの link:_set.html#so-unset[unset オプション]が無効な場合、{{パラメータ名}}に存在しない変数を指定すると展開エラーになります。Unset オプションが有効な場合は、存在しない変数は空文字列に展開されます。 より複雑なパラメータ展開の形式では、パラメータの値を加工することができます。パラメータ展開の一般形は以下の通りです。 パラメータ展開:: +${ {{前置詞}} {{パラメータ名}} {{インデックス}} {{加工指定}} }+ ここでは便宜上{{パラメータ名}}や{{インデックス}}の周りに空白を入れましたが、実際には空白を入れてはいけません。{{パラメータ名}}以外の部分はいずれも省略可能です。 [[param-prefix]] === 前置詞 {{前置詞}}として{{パラメータ名}}の直前に記号 +#+ を置くことができます。この場合、このパラメータ展開はいま展開しようとしている値の文字数を表す整数に展開されます。展開しようとしているのが配列変数の場合、各要素がそれぞれ文字数を表す整数に置き換えられます。 [[param-name]] === パラメータ名 {{パラメータ名}}には、{zwsp}link:params.html#special[特殊パラメータ]・{zwsp}link:params.html#positional[位置パラメータ]・変数を指定することができます。この場合、パラメータ展開は指定されたパラメータの値に展開されます。指定した{{パラメータ名}}が{zwsp}link:params.html#arrays[配列]変数の場合、配列の各要素が特殊パラメータ +@+ の場合と同様に単語分割されます (インデックス +[*]+ が指定された場合を除く)。 {{パラメータ名}}としてパラメータ展開・<>・<>を指定することもできます。これは特にdfn:[展開の入れ子]と言います。この場合、パラメータ展開は内側の展開の展開結果に展開されます。なお、内側のパラメータ展開の括弧 +{ }+ は省略できません。また展開の入れ子は link:posix.html[POSIX 準拠モード]では使えません。 [[param-index]] === インデックス {{インデックス}}は展開する値の一部を抜き出すのに使います。インデックスは以下の書式をしています。 インデックス:: +[{{単語1}}]+ + +[{{単語1}},{{単語2}}]+ ここでの{{単語1}}および{{単語2}}は通常の{zwsp}link:syntax.html#tokens[トークン]と同様に解釈されますが、+,+ と +]+ で強制的に区切られます。また空白やタブはトークンの区切りとはみなしません。 インデックスは、以下のように解釈されます。 . まず、{{インデックス}}に含まれる{{単語1}}・{{単語2}}に対してパラメータ展開・<>・<>を行います。 . {{インデックス}}が +[{{単語1}}]+ の書式をしていて、{{単語1}}の上記展開結果が ++*++、++@++、++#++ のいずれかの場合は、インデックスの解釈は終了です。 . {{単語1}}と{{単語2}}の上記展開結果を数式とみなして、数式展開と同様に計算します。計算の結果得られる整数がインデックスとなります。数式展開の結果が整数でない場合は展開エラーです。{{単語2}}がない形式でインデックスを指定している場合は、{{単語2}}は{{単語1}}と同じ整数を指定しているものとみなされます。 {{パラメータ名}}が{zwsp}link:params.html#arrays[配列]変数の場合または特殊パラメータ link:params.html#sp-asterisk[+*+] または link:params.html#sp-at[+@+] の場合、インデックスは配列の要素または位置パラメータの一部を指定しているものとみなされます。{{パラメータ名}}が上記以外の場合は、パラメータの値の一部を指定しているものとみなされます。インデックスで選択された配列の要素またはパラメータの値の一部のみが、パラメータ展開の結果として展開結果に残ります。インデックスによる選択について以下の規則が適用されます。 - インデックスの整数が負数のときは、要素または文字を最後から数えるものとみなされます。例えばインデックス +[-2,-1]+ は配列の最後の二つの要素 (またはパラメータの値の最後の 2 文字) を選択します。 - インデックスの整数が存在しない要素または文字を指示している場合でも、エラーにはなりません。例えば配列の要素数が 4 のときにインデックス +[3,5]+ が与えられたときは 3 番目以降の全ての要素が選択され、インデックス +[5,7]+ が与えられた時はどの要素も選択されません。 - インデックスの整数の一方が 0 ならば、(もう一方が何であれ) 展開結果には何も残りません。 {{インデックス}}が +[{{単語1}}]+ の書式をしていて、{{単語1}}の展開結果が ++*++、++@++、++#++ のいずれかだった場合は、パラメータは以下のように処理されます。 +*+:: {{パラメータ名}}が配列変数の場合、{zwsp}link:params.html#sp-asterisk[特殊パラメータ +*+] と同様に配列の全要素を{zwsp}link:expand.html#split[単語分割]または連結します。{{パラメータ名}}が特殊パラメータ +*+ または +@+ の場合、同様に位置パラメータを単語分割・連結します。それ以外の場合はインデックス +[1,-1]+ と同様です。 +@+:: インデックス +[1,-1]+ と同様です。 +#+:: {{パラメータ名}}が配列変数の場合、このパラメータ展開は配列の要素数を表す整数に展開されます。{{パラメータ名}}が特殊パラメータ +*+ または +@+ の場合、このパラメータ展開は位置パラメータの個数を表す整数に展開されます。それ以外の場合、このパラメータ展開はいま展開しようとしている値の文字数を表す整数に展開されます。 パラメータ展開に{{インデックス}}が指定されていない場合は、{{インデックス}}として +[@]+ が指定されたものとみなされます。{{インデックス}}は{zwsp}link:posix.html[POSIX 準拠モード]では一切使えません。 .通常の変数の展開 ==== 以下のコマンドは文字列 +ABC+ を出力します: ---- var='123ABC789' echo "${var[4,6]}" ---- ==== .位置パラメータの展開 ==== 以下のコマンドは文字列 +2 3 4+ を出力します: ---- set 1 2 3 4 5 echo "${*[2,-2]}" ---- ==== .配列の展開 ==== 以下のコマンドは文字列 +2 3 4+ を出力します: ---- array=(1 2 3 4 5) echo "${array[2,-2]}" ---- ==== [[param-mod]] === 加工指定 {{加工指定}}はパラメータの値を加工します。加工された後の値がパラメータ展開の結果として展開されます。加工指定には以下の形式があります。 +-{{単語}}+:: {{パラメータ名}}が存在しない変数を指示している場合は、このパラメータ展開は{{単語}}に展開されます。(link:_set.html#so-unset[Unset オプション]が無効な時でもエラーになりません) ++{{単語}}+:: {{パラメータ名}}が存在する変数を指示している場合は、このパラメータ展開は{{単語}}に展開されます。(link:_set.html#so-unset[Unset オプション]が無効な時でもエラーになりません) +={{単語}}+:: {{パラメータ名}}が存在しない変数を指示している場合は、{{単語}}の展開結果がその変数に代入された後、このパラメータ展開はその値に展開されます。変数以外のものに対して代入しようとすると展開エラーになります。(link:_set.html#so-unset[Unset オプション]が無効な時でもエラーになりません) +?{{単語}}+:: {{パラメータ名}}が存在しない変数を指示している場合は、エラーメッセージとして{{単語}}を標準エラーに出力します。({{単語}}がない場合はデフォルトのエラーメッセージが出ます) +:-{{単語}}+:: +:+{{単語}}+:: +:={{単語}}+:: +:?{{単語}}+:: これらは上記の ++-++、+++++、++=++、++?++ と{{単語}}の組み合わせの加工指定と同様ですが、{{単語}}を使用する条件が異なります。先頭に +:+ が付かないものでは ``変数が存在するかどうか'' で判定されますが、+:+ が付くものでは ``変数が存在し、その値が空文字列でないかどうか'' で判定されます。 +#{{単語}}+:: {{単語}}を{zwsp}link:pattern.html[パターン]として見たとき、それがいま展開しようとしている値の先頭部分にマッチするならば、そのマッチする部分を削除します。結果として、このパラメータ展開はマッチしなかった残りの部分に展開されます。マッチの仕方が複数通りある場合はできるだけ短くマッチさせます。 +##{{単語}}+:: この加工指定は +#{{単語}}+ と同様ですが、マッチの仕方が複数通りある場合はできるだけ長くマッチさせる点が異なります。 +%{{単語}}+:: この加工指定は +#{{単語}}+ と同様ですが、値の先頭部分ではなく末尾部分にマッチさせる点が異なります。 +%%{{単語}}+:: この加工指定は +%{{単語}}+ と同様ですが、マッチの仕方が複数通りある場合はできるだけ長くマッチさせる点が異なります。 +/{{単語1}}/{{単語2}}+:: {{単語1}}をパターンとして見たとき、それがいま展開しようとしている値の一部にマッチするならば、そのマッチする部分を{{単語2}}に置き換えます。結果として、このパラメータ展開はマッチした部分を{{単語2}}に置き換えた値に展開されます。マッチする箇所が複数ある場合は、最初の箇所が選ばれます。マッチの仕方が複数通りある場合はできるだけ長くマッチさせます。 + この加工指定は link:posix.html[POSIX 準拠モード]では使えません。 +/#{{単語1}}/{{単語2}}+:: この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の先頭部分にしかマッチしない点が異なります。 +/%{{単語1}}/{{単語2}}+:: この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の末尾部分にしかマッチしない点が異なります。 +//{{単語1}}/{{単語2}}+:: この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、マッチする箇所が複数ある場合は最初の箇所だけではなく全ての箇所を{{単語2}}に置き換える点が異なります。 +:/{{単語1}}/{{単語2}}+:: この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値全体にマッチする場合しか対象としない点が異なります。 いずれの形式においても、加工指定に含まれる単語は (それが使用されるときのみ) 四種展開されます。 展開しようとしている{{パラメータ名}}が配列変数または特殊パラメータ {zwsp}link:params.html#sp-at[+@+] または link:params.html#sp-asterisk[+*+] の場合、加工指定は配列の各要素または各位置パラメータに対してそれぞれ作用します。 [[cmdsub]] == コマンド置換 dfn:[コマンド置換]は、指定されたコマンドを実行してその出力をコマンドラインに展開します。コマンド置換の書式は以下の通りです。 コマンド置換:: +$({{コマンド}})+ + +`{{コマンド}}`+ コマンド置換では、{{コマンド}}が{zwsp}link:exec.html#subshell[サブシェル]で実行されます。このときコマンドの標準出力がパイプを通じてシェルに送られます。結果として、コマンド置換はコマンドの出力結果に置き換えられます。ただし、コマンドの出力の末尾にある改行は除きます。 +$(+ と +)+ で囲んだコマンド置換の{{コマンド}}は、コマンド置換の入れ子やリダイレクトなどを考慮して予め解析されます。従って、+$(+ と +)+ の間には基本的に通常通りコマンドを書くことができます。ただし、<>との混同を避けるため、中の{{コマンド}}が +(+ で始まる場合は{{コマンド}}の最初に空白を挿し挟んでください。 +`+ で囲むコマンド置換では、コマンド置換の入れ子などは考慮せずに、{{コマンド}}の中に最初に (バックスラッシュで{zwsp}link:syntax.html#quotes[クォート]していない) +`+ が現れたところでコマンド置換の終わりとみなされます。+`+ で囲んだコマンド置換の中に +`+ で囲んだコマンド置換を書く場合は、内側の +`+ をバックスラッシュでクォートする必要があります。その他、{{コマンド}}の一部として +`+ を入れたいときは、(それが{{コマンド}}内部で一重または二重引用符でクォートされていても) バックスラッシュでクォートする必要があります。{{コマンド}}の中ではバックスラッシュは ++$++・++`++・バックスラッシュ・改行の直前にある場合のみ引用符として扱われます。また、++`++ で囲んだコマンド置換が二重引用符の中で使われる場合は、{{コマンド}}の中に現れる二重引用符もバックスラッシュでクォートする必要があります。これらのバックスラッシュは{{コマンド}}が解析される前に削除されます。 +$(+ と +)+ で囲んだコマンド置換の中のコマンドは、そのコマンド置換を含むコマンドを解析する時に一緒に解析されます (link:posix.html[POSIX 準拠モード]を除く)。+`+ で囲んだコマンド置換の中のコマンドは、POSIX 準拠モードであるかどうかに関わらず、そのコマンド置換が実行される時に毎回解析されます。 [[arith]] == 数式展開 dfn:[数式展開]は、文字列を数式として解釈して、その計算結果を表す数値に展開します。数式展開の書式は以下の通りです。 数式展開:: +$(({{式}}))+ 数式展開では、まず{{式}}に対して<>・<>・(入れ子の) 数式展開が行われます。その結果得られた文字列を以下のように数式として解釈し、その計算結果を表す数値に展開されます。 Yash では、数式の中で整数 (C 言語の long 型) と浮動小数点数 (C 言語の double 型) を扱うことができます。ただし link:posix.html[POSIX 準拠モード]では浮動小数点数は使えません。整数同士の演算の結果は整数に、浮動小数点数を含む演算の結果は浮動小数点数になります。 数式では C 言語と (ほぼ) 同様に以下の演算子が使えます。 . +( )+ . `++` +--+ (後置演算子) . `++` +--+ `+` +-+ +~+ +!+ (前置演算子) . +*+ +/+ +%+ . `+` +-+ (二項演算子) . +<<+ +>>+ . +<+ +<=+ +>+ +>=+ . +==+ +!=+ . +&+ . +^+ . +|+ . +&&+ . +||+ . +? :+ (三項演算子) . +=+ +*=+ +/=+ +%=+ `+=` +-=+ +<<=+ +>>=+ +&=+ +^=+ +|=+ `++` および `--` 演算子は POSIX 準拠モードでは使えません。 原子式としては整数リテラル・浮動小数点数リテラル・変数が使用できます。数リテラルの書式は C 言語に準じます。+0+ で始まる整数リテラルは八進数、+0x+ で始まる整数リテラルは十六進数とみなされます。浮動小数点数リテラルでは指数表記も使えます (例えば 1.23×10^6^ は +1.23e+6+)。変数の値が計算で使われるとき、その値が数値でない場合はエラーになります。link:_set.html#so-unset[Unset オプション]が有効ならば未定義の変数は 0 とみなします。 POSIX 準拠モードでは、変数は必ず数値として解釈されます。 POSIX 準拠モードでないときは、計算で使われる変数のみが数値として解釈され、他の変数はそのまま残ります。 ---- set +o posixly-correct foo=bar echo $((0 ? foo : foo)) # 「bar」を出力 echo $((foo + 0)) # エラー ---- [[brace]] == ブレース展開 dfn:[ブレース展開]は、ブレース (+{ }+) で囲んだ部分をいくつかの単語に分割します。ブレース展開は link:_set.html#so-braceexpand[brace-expand オプション]が有効な時のみ行われます。ブレース展開には二種類の形式があります。 カンマ区切りのブレース展開:: +{{{単語1}},{{単語2}},...,{{単語n}}}+ 連続した数値のブレース展開:: +{{{始点}}..{{終点}}}+ + +{{{始点}}..{{終点}}..{{差分}}}+ 一つ目の形式は、ブレースで囲んだ部分を一つ以上のカンマ (+,+) で区切ったものです。区切られたそれぞれの部分がブレース展開の前後の部分と結合されて、それぞれ単語として展開されます。例えば +a{1,2,3}b+ は ++a1b++、++a2b++、++a3b++ という三つの単語に展開されます。 二つ目の形式は +{{{始点}}..{{終点}}}+ または +{{{始点}}..{{終点}}..{{差分}}}+ です。{{始点}}・{{終点}}・{{差分}}は全て整数である必要があります。この形式のブレース展開では、{{始点}}から{{終点}}までの各整数がブレース展開の前後の部分と結合されて、それぞれ単語として展開されます。{{差分}}は整数の間隔を指定します。例えば +a{1..3}b+ は ++a1b++、++a2b++、++a3b++ という三つの単語に展開され、+a{1..7..2}b+ は ++a1b++、++a3b++、++a5b++、++a7b++ という四つの単語に展開されます。{{始点}}が{{終点}}より大きい場合は整数は降順に展開されます。 複数のブレース展開を組み合わせたり、入れ子にしたりすることもできます。ブレースをブレース展開としてでなく通常の文字として扱うには、ブレースをクォートしてください。またカンマを区切りとしてでなく通常の文字として扱うには、カンマをクォートしてください。 ブレース展開では展開エラーは発生しません。ブレース展開が正しくできない場合は、単にそれはブレース展開ではなかったものとして、そのまま残されます。 [[split]] == 単語分割 dfn:[単語分割]は、展開の結果をいくつかの単語に分割します。 単語分割で分割の対象となるのは、<>・<>・<>で展開された結果の部分だけです。また、二重引用符による{zwsp}link:syntax.html#quotes[クォート]の中で展開された部分は、(link:params.html#sp-at[特殊パラメータ +@+] の展開を除いて) 分割の対象となりません。 単語分割は link:params.html#sv-ifs[+IFS+ 変数]の値に従って行われます。+IFS+ 変数が存在しない場合は、空白文字・タブ・改行の三文字が +IFS+ 変数の値として使われます。 +IFS+ 変数の値に含まれている文字を dfn:[IFS 文字]といいます。IFS 文字のうち空白文字またはタブまたは改行であるものを dfn:[IFS 空白類]といいます。IFS 空白類以外の IFS 文字を dfn:[IFS 非空白類]といいます。 分割は以下の規則に従って行われます。 . 分割は、分割の対象となる展開結果の部分の中で、IFS 文字が現れる箇所で行われます。以下このような箇所をdfn:[分割点]と呼びます。複数の IFS 文字が連続して現れる場合は、それらをまとめて一つの分割点とします。 . 分割点に IFS 非空白類が含まれている場合、その分割点に含まれる IFS 空白類はすべて無視されます。そして分割点に含まれる各 IFS 非空白類の前後で単語が分割されます。 . 分割点に IFS 非空白類が含まれていない (分割点が IFS 空白類だけからなる) 場合、その分割点の前後で単語が分割されます。ただし、このような分割点が元の単語の先頭または末尾にある場合を除きます。 . いずれの場合も、分割点は単語分割後の単語には残りません。 最後に、以下の条件がすべて成り立つならば、分割された最後の単語が結果から削除されます。 - link:_set.html#so-emptylastfield[empty-last-field オプション]が無効 - 分割結果が複数の単語である - 最後の単語が空文字列である [NOTE] +IFS+ 変数の値が空文字列の場合は、単語は一切分割されません。 [[glob]] == パス名展開 dfn:[パス名展開]は、単語を{zwsp}link:pattern.html[パターン]とみなしてファイルを検索し、パターンにマッチする実在のファイルへのパス名に展開します。 パス名展開は link:_set.html#so-glob[glob オプション]が無効な時は行われません。 パス名展開においてパターンがマッチするには、検索の対象となるディレクトリの読み込み権限が必要です。検索しようとしたディレクトリがシェルにとって読み込み可能でなければ、シェルはそのディレクトリは空であるとみなします。 以下のオプションがパス名展開の結果に影響します。 [[opt-nullglob]]null-glob:: マッチするファイルがない時、通常 (このオプションが無効な時) はパターンはそのまま残りますが、このオプションが有効な時はパターンは削除され何も残りません。 [[opt-caseglob]]case-glob:: 通常 (このオプションが有効な時) は、大文字と小文字を区別してマッチングを行いますが、このオプションが無効な時は大文字と小文字を区別せずマッチングを行います。 [[opt-dotglob]]dot-glob:: 通常 (このオプションが無効な時) は、+*+ や +?+ などのワイルドカードやブラケット記法で始まるパターンはピリオドで始まるファイル名にマッチしません。しかしこのオプションが有効な時はこのような制約は解除されます。 [[opt-markdirs]]mark-dirs:: このオプションを有効にすると、マッチしたファイルの種類がディレクトリの場合に展開されるパス名の最後に +/+ が付きます。 [[opt-extendedglob]]extended-glob:: ifdef::basebackend-html[] このオプションを有効にすると、パス名展開における<>が使えるようになります。 endif::basebackend-html[] ifdef::basebackend-docbook[] このオプションを有効にすると、パス名展開における拡張機能 (後述) が使えるようになります。 endif::basebackend-docbook[] パス名展開ではエラーは発生しません。マッチするファイルがない場合またはパターンが不正な場合は、展開は行われずパターンはそのまま残ります (null-glob オプションが有効な時を除く)。 ファイルの検索とパターンマッチングは +/+ で区切られたパス名の構成要素ごとに行われます。ワイルドカードやブラケット記法を全く含まない構成要素はパターンとはみなされず、検索とマッチングは行われません。従って、case-glob オプションが無効な時、+/*/foo+ と +/*/fo[o]+ の展開結果が異なる可能性があります (前者では +foo+ の部分がパターンとはみなされないので、例えば /bar/FOO というファイルがあってもマッチしません。)。 [[extendedglob]] === パス名展開の拡張機能 <>が有効な時は、以下の特殊なパターンが使えるようになります。 +**+:: 指定されたディレクトリツリーに対し再帰的に検索を行います。すなわち、指定されたディレクトリと、そのサブディレクトリ、さらにそのサブディレクトリなどに対し検索を行います。ただし名前がピリオドで始まるディレクトリは検索の対象になりません。例えば +dir/**/file+ というパターンは、dir/file や dir/foo/file や dir/a/b/c/file など、dir ディレクトリの中にある全ての file ファイルへのパスに展開されます。 + この特殊なパターンは、 +foo/bar/**+ のようにパターン全体の最後にある場合には効果がありません。 +.**+:: +**+ パターンと同様ですが、名前がピリオドで始まるディレクトリも含めて検索する点が異なります。 +***+:: +**+ パターンと同様ですが、検索の中でディレクトリへのシンボリックリンクが見つかった場合、そのディレクトリの中も検索の対象に含める点が異なります。 +.***+:: +***+ パターンと同様ですが、名前がピリオドで始まるディレクトリも含めて検索する点が異なります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/faq.txt000066400000000000000000000057331354143602500150760ustar00rootroot00000000000000= よくある質問 / 困ったときは :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - よくある質問 / 困ったときは :description: Yash についてのよくある質問とその答え [[unicode]] == Unicode (非 ASCII) 文字を使うには? Unicode 文字を使えるようにするにはロケール環境変数の設定が必要です。 既に環境変数があなたの使用したいロケールに合わせて設定されている場合は、特に何もする必要はありません。現在の設定を確認するには、+locale+ コマンドを使用してください。 ---- $ locale LANG=ja_JP.utf8 LC_CTYPE="ja_JP.utf8" LC_NUMERIC="ja_JP.utf8" LC_TIME="ja_JP.utf8" LC_COLLATE="ja_JP.utf8" LC_MONETARY="ja_JP.utf8" LC_MESSAGES="ja_JP.utf8" LC_PAPER="ja_JP.utf8" LC_NAME="ja_JP.utf8" LC_ADDRESS="ja_JP.utf8" LC_TELEPHONE="ja_JP.utf8" LC_MEASUREMENT="ja_JP.utf8" LC_IDENTIFICATION="ja_JP.utf8" LC_ALL= ---- この例の +locale+ コマンドの出力結果は、各ロケール設定カテゴリーについて、言語が日本語、地域が日本、エンコーディングが UTF-8 に設定されていることを示しています。 現在の設定が自分の使いたいロケール設定に合っていない場合は、+LC_ALL+ 変数を以下の様に設定してください。 ---- export LC_ALL=ja_JP.utf8 ---- この例とは異なる言語・地域・エンコーディングの設定を使いたい場合は、設定値を変える必要があります。詳しくは、お使いのオペレーティングシステムの説明書を参照してください。 Yash を起動する度に同じ設定を自動的に適用するには、上記のコマンドを ~/.yashrc か ~/.yash_profile に書いてください。 上記の設定をしてもなお Unicode 文字が正しく入力できない場合は、以下の行編集に関する項目を読んでください。 [[lineediting]] == 行編集が動かない まず、+echo $TERM+ を実行して出力される値が正常かどうか調べてください。値が +xterm+ なら、おそらくどんな環境でも最低限の機能は使えるはずです。 +xterm-16color+ などの色が使えるバージョンや、+rxvt+ や +vt100+ などの他の端末機種も大概は動きます。利用可能な全ての値を列挙するには +toe+ コマンドを使用してください。 お使いの端末機種に合った値を設定するのが理想ですが、yash を実行しようとしている環境がその端末機種に対応していない場合はうまく動かないかもしれません。その場合は、+export TERM=xterm+ などを実行して +TERM+ の値を変えてみてください。 行編集自体は動いているが Unicode (非 ASCII) 文字の入力に問題がある場合は、+set -o le-no-conv-meta+ を実行して link:lineedit.html#options[le-no-conv-meta] オプションを有効にしてみてください。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/fgrammar.txt000066400000000000000000000264571354143602500161310ustar00rootroot00000000000000= 構文の形式的定義 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - 構文の形式的定義 :description: Yash のコマンドの構文規則の形式的な定義 ここにプログラミング言語としてのシェルの構文定義を示します。 [NOTE] 以下に示す構文の一部は link:posix.html[POSIX 準拠モード]では使用できません。 [[token]] == トークン分割 入力ソースコードの文字列はまずトークンに分割されます。トークンはソースコード内のより先に現れるトークンができるだけ長くなるように分割します。一つ以上の空白 (blank) 文字の連なりはトークンを分割します。 Yash に存在する演算子トークンは以下の通りです: `&` `&&` `(` `)` `;` `;;` `|` `||` `<` `<<` `<&` `<(` `<<-` `<<<` `<>` `>` `>>` `>&` `>(` `>>|` `>|` (改行) [NOTE] 他の一般的なプログラミング言語とは異なり、改行演算子は空白ではなくトークンとして扱われます。 空白ではなく演算子トークンの一部でもない文字は単語 (word) トークンとなります。単語は以下の解析表現文法によって解析されます。 [[d-word]]Word:: (<> / !<> .)+ [[d-word-element]]WordElement:: +\+ . / + `'` (!`'` .)* `'` / + +"+ <>* +"+ / + <> / + <> / + <> [[d-quote-element]]QuoteElement:: +\+ ([+$`"\+] / <改行>) / + <> / + <> / + <> / + ![+`"\+] . [[d-parameter]]Parameter:: +$+ [+@*#?-$!+ [:digit:]] / + +$+ <> / + +$+ <> [[d-portable-name]]PortableName:: ![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+ [[d-parameter-body]]ParameterBody:: +{+ <>? (<> / ParameterBody / +$+ ParameterBody / <> / <>) <>? <>? +}+ [[d-parameter-number]]ParameterNumber:: `#` ![`}+=:/%`] !([`-?#`] !`}`) [[d-parameter-name]]ParameterName:: [+@*#?-$!+] / + [[:alnum:] +_+]+ [[d-parameter-index]]ParameterIndex:: +[+ <> (+,+ ParameterIndexWord)? +]+ [[d-parameter-index-word]]ParameterIndexWord:: (<> / ![+"'],+] .)+ [[d-parameter-match]]ParameterMatch:: `:`? [`-+=?`] <> / + (`#` / `##` / `%` / `%%`) ParameterMatchWord / + (`:/` / `/` [`#%/`]?) <> (+/+ ParameterMatchWord)? [[d-parameter-match-word]]ParameterMatchWord:: (<> / ![+"'}+] .)* [[d-parameter-match-word-no-slash]]ParameterMatchWordNoSlash:: (<> / ![+"'/}+] .)* [[d-arithmetic]]Arithmetic:: `$((` <>* `))` [[d-arithmetic-body]]ArithmeticBody:: +\+ . / + <> / + <> / + <> / + +(+ ArithmeticBody +)+ / + ![+`()+] . [[d-command-substitution]]CommandSubstitution:: +$(+ <> +)+ / + +`+ <>* +`+ [[d-command-substitution-quoted]]CommandSubstitutionQuoted:: +$(+ <> +)+ / + +`+ <>* +`+ [[d-command-substitution-body]]CommandSubstitutionBody:: +\+ [+$`\+] / + !++`++ . [[d-command-substitution-body-quoted]]CommandSubstitutionBodyQuoted:: +\+ [+$`\`+] / + !++`++ . [[d-special-char]]SpecialChar:: [+|&;<>()`\"'+ [:blank:]] / <改行> この文法における終端記号の集合は、yash を実行する環境が扱える任意の文字の集合 (実行文字集合) です (ただしナル文字 `'\0'` を除く)。 厳密には、上記の文法定義は完全な解析表現文法ではありません。<> (<>) のルールが構文定義の非終端記号である <> に依存しているためです。 [[classification]] === トークン分類 単語トークンが生成された後、単語はさらに IO_NUMBER トークン・予約語・名前・代入・通常の単語のどれかに分類されます。通常の単語以外の分類は、その単語が現れる文脈においてその分類のトークンが現れ得る場合のみ採用されます。予約語の一覧と予約語が認識される文脈の要件は、{zwsp}link:syntax.html#tokens[トークンの解析と予約語]を参照してください。 トークンが数字のみから構成されていて直後に +<+ または +>+ が続くとき、それは IO_NUMBER トークンとなります。 代入 (assignment) トークンは名前 (name) とそれに続く +=+ で始まるトークンです: [[d-assignment-word]]AssignmentWord:: <> <> [[d-assignment-prefix]]AssignmentPrefix:: <> +=+ [[d-name]]Name:: !\[[:digit:]] \[[:alnum:] +_+]+ [[comments]] === コメント コメントは `#` で始まり、次の改行文字の直前まで続きます。コメントは空白と同様に扱われ、トークンの一部にはなりません。コメントを開始する `#` は、トークンの始まりの位置にあるときのみ有効です。それ以外の位置にある `#` は単に単語トークンの一部と見做されます。 [[d-comment]]Comment:: `#` (!<改行> .)* [[syntax]] == 構文 トークンが分割された後、その結果であるトークンの並びは以下に示す文脈自由文法によって解析されます。(以下、`*` と `+` と `?` は正規表現と同様の意味を持ちます) [[d-complete-program]]CompleteProgram:: <>* | <> [[d-compound-list]]CompoundList:: <>* <> ({zwsp}(+;+ | +&+ | NL) <>)? [[d-and-or-list]]AndOrList:: <> ((+&&+ | +||+) <>* Pipeline)* [[d-pipeline]]Pipeline:: +!+? <> (+|+ <>* Command)* [[d-command]]Command:: <> <>* | + <> | + <> [[d-compound-command]]CompoundCommand:: <> | + <> | + <> | + <> | + <> | + <> | + <> | + <> [[d-subshell]]Subshell:: +(+ <> +)+ [[d-grouping]]Grouping:: +{+ <> +}+ [[d-if-command]]IfCommand:: +if+ <> +then+ CompoundList (+elif+ CompoundList +then+ CompoundList)* (+else+ CompoundList)? +fi+ [[d-for-command]]ForCommand:: +for+ <> ({zwsp}(<>* +in+ <>*)? (+;+ | NL) NL*)? +do+ <> +done+ [[d-while-command]]WhileCommand:: (+while+ | +until+) <> +do+ CompoundList +done+ [[d-case-command]]CaseCommand:: +case+ <> <>* +in+ NL* <>? +esac+ [[d-case-list]]CaseList:: <> (+;;+ <>* CaseList)? [[d-case-item]]CaseItem:: +(+? <> (+|+ Word)* +)+ <> [[d-double-bracket-command]]DoubleBracketCommand:: +[[+ <> +]]+ [[d-ors]]Ors:: <> (+||+ Ands)* [[d-ands]]Ands:: <> (+&&+ Nots)* [[d-nots]]Nots:: ++!++* <> [[d-primary]]Primary:: (+-b+ | +-c+ | +-d+ | +-e+ | +-f+ | +-G+ | +-g+ | +-h+ | +-k+ | +-L+ | +-N+ | +-n+ | +-O+ | +-o+ | +-p+ | +-r+ | +-S+ | +-s+ | +-t+ | +-u+ | +-w+ | +-x+ | +-z+) <> | + Word (+-ef+ | +-eq+ | +-ge+ | +-gt+ | +-le+ | +-lt+ | +-ne+ | +-nt+ | +-ot+ | +-veq+ | +-vge+ | +-vgt+ | +-vle+ | +-vlt+ | +-vne+ | +=+ | +==+ | +===+ | +=~+ | +!=+ | +!==+ | +<+ | +>+) Word | + +(+ <> +)+ | + Word [[d-function-command]]FunctionCommand:: +function+ <> (+(+ +)+)? <>* <> <>* [[d-function-definition]]FunctionDefinition:: <> +(+ +)+ <>* <> <>* [[d-simple-command]]SimpleCommand:: (<> | <>) SimpleCommand? | + <> (Word | <>)* [[d-assignment]]Assignment:: <> | + <>++(++ <>* (<> NL*)* +)+ [[d-redirection]]Redirection:: IO_NUMBER? <> <> | + IO_NUMBER? +<(+ <> +)+ | + IO_NUMBER? +>(+ CompleteProgram +)+ [[d-redirection-operator]]RedirectionOperator:: `<` | `<>` | `>` | `>|` | `>>` | `>>|` | `<&` | `>&` | `<<` | `<<-` | `<<<` [[d-nl]]NL:: <改行> ルール <> では、<> トークンは +]]+ に一致してはなりません。また、Primary が Word トークンで始まる場合にはその Word は同ルールで認められている単項演算子に一致してはなりません。 ルール <> では、<> トークンはそれが <> の始まりとは解釈できない場合にのみ採用されます。 ルール <> では、<> と ++(++ の間に空白を置くことはできません。 上記の文法定義には{zwsp}link:redir.html#here[ヒアドキュメント]の内容とその終わりを表す行の解析のための規則は含まれていません。それらは対応するリダイレクト演算子の後にある最初の改行 (<>) トークンが解析された直後に解析されます。 [[alias]] === エイリアス置換 単語は{zwsp}link:syntax.html#aliases[エイリアス置換]の対象となります。 - 単語が <> の <> として解析されようとした時に、通常のエイリアス及びグローバルエイリアスを対象として置換が試みられます。 - 置換結果が空白文字 (blank) で終わるエイリアス置換の次に単語トークンがある場合、その単語も通常のエイリアス及びグローバルエイリアスを対象として置換が試みられます。 - その他の単語は、グローバルエイリアスのみを対象として置換が試みられます。(link:posix.html[POSIX 準拠モード]を除く) 予約語に分類されたトークンはエイリアス置換の対象からは除外されます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/index.txt.in000066400000000000000000000110551354143602500160350ustar00rootroot00000000000000= Yet another shell (yash) マニュアル 渡邊裕貴 v{yashversion}, :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - Yet another shell (yash) マニュアル :description: Yash のマニュアルの目次 [[contents-list]] == 目次 [role="list-group"] -- // (Replaced with generated contents list) // -- [[builtins]] == 組込みコマンド `*' は特殊組込みコマンドを、 `+' は準特殊組込みコマンドを表します。 (link:builtin.html#types[組込みコマンドの種類]を参照) [[alpha-order]] === 組込みコマンド一覧 (アルファベット順) [role="list-group"] - link:_dot.html[+.+ (ドット)] * - link:_colon.html[+:+ (コロン)] * - link:_test.html[+[+ (括弧)] - link:_alias.html[+alias+] + - link:_array.html[+array+] - link:_bg.html[+bg+] + - link:_bindkey.html[+bindkey+] + - link:_break.html[+break+] * - link:_cd.html[+cd+] + - link:_command.html[+command+] + - link:_complete.html[+complete+] + - link:_continue.html[+continue+] * - link:_dirs.html[+dirs+] + - link:_disown.html[+disown+] + - link:_echo.html[+echo+] - link:_eval.html[+eval+] * - link:_exec.html[+exec+] * - link:_exit.html[+exit+] * - link:_export.html[+export+] * - link:_false.html[+false+] + - link:_fc.html[+fc+] + - link:_fg.html[+fg+] + - link:_getopts.html[+getopts+] + - link:_hash.html[+hash+] + - link:_help.html[+help+] + - link:_history.html[+history+] + - link:_jobs.html[+jobs+] + - link:_kill.html[+kill+] + - link:_local.html[+local+] + - link:_popd.html[+popd+] + - link:_printf.html[+printf+] - link:_pushd.html[+pushd+] + - link:_pwd.html[+pwd+] + - link:_read.html[+read+] + - link:_readonly.html[+readonly+] * - link:_return.html[+return+] * - link:_set.html[+set+] * - link:_shift.html[+shift+] * - link:_suspend.html[+suspend+] + - link:_test.html[+test+] - link:_times.html[+times+] * - link:_trap.html[+trap+] * - link:_true.html[+true+] + - link:_type.html[+type+] + - link:_typeset.html[+typeset+] + - link:_ulimit.html[+ulimit+] + - link:_umask.html[+umask+] + - link:_unalias.html[+unalias+] + - link:_unset.html[+unset+] * - link:_wait.html[+wait+] + [[groups]] === 種類別組込みコマンド一覧 [[g-control]] ==== 実行制御 [role="list-group"] - link:_eval.html[+eval+] * - link:_dot.html[+.+ (ドット)] * - link:_exec.html[+exec+] * - link:_command.html[+command+] + - link:_hash.html[+hash+] - link:_break.html[+break+] * - link:_continue.html[+continue+] * - link:_return.html[+return+] * - link:_suspend.html[+suspend+] - link:_exit.html[+exit+] * [[g-environ]] ==== コマンド実行環境関連 [role="list-group"] - link:_set.html[+set+] * - link:_ulimit.html[+ulimit+] - link:_umask.html[+umask+] + - link:_trap.html[+trap+] * - link:_cd.html[+cd+] + - link:_pwd.html[+pwd+] + - link:_times.html[+times+] * [[g-job]] ==== ジョブ制御・シグナル関連 [role="list-group"] - link:_jobs.html[+jobs+] + - link:_fg.html[+fg+] + - link:_bg.html[+bg+] + - link:_wait.html[+wait+] + - link:_disown.html[+disown+] - link:_kill.html[+kill+] + - link:_trap.html[+trap+] * [[g-variable]] ==== パラメータ・変数関連 [role="list-group"] - link:_typeset.html[+typeset+] - link:_export.html[+export+] * - link:_local.html[+local+] + - link:_readonly.html[+readonly+] * - link:_array.html[+array+] - link:_set.html[+set+] * - link:_shift.html[+shift+] * - link:_read.html[+read+] + - link:_getopts.html[+getopts+] + - link:_unset.html[+unset+] * [[g-directory]] ==== 作業ディレクトリ関連 [role="list-group"] - link:_cd.html[+cd+] + - link:_pwd.html[+pwd+] + - link:_pushd.html[+pushd+] - link:_popd.html[+popd+] - link:_dirs.html[+dirs+] [[g-alias]] ==== エイリアス関連 [role="list-group"] - link:_alias.html[+alias+] + - link:_unalias.html[+unalias+] + [[g-history]] ==== コマンド履歴関連 [role="list-group"] - link:_fc.html[+fc+] + - link:_history.html[+history+] [[g-print]] ==== 文字列出力 [role="list-group"] - link:_echo.html[+echo+] - link:_printf.html[+printf+] [[g-lineedit]] ==== 行編集関連 [role="list-group"] - link:_bindkey.html[+bindkey+] - link:_complete.html[+complete+] [[g-misc]] ==== その他のコマンド [role="list-group"] - link:_help.html[+help+] - link:_colon.html[+:+ (コロン)] * - link:_true.html[+true+] + - link:_false.html[+false+] + - link:_test.html[+[+ (括弧), +test+] - link:_type.html[+type+] // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/interact.txt000066400000000000000000000401541354143602500161340ustar00rootroot00000000000000= 対話モード :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - 対話モード :description: Yash の対話モードについて dfn:[対話モード]は、利用者が直接シェルを操作することを意図したモードです。{zwsp}link:invoke.html[シェルの起動]時に +-i+ オプションを指定した場合 (その他対話モードが有効になる条件が満たされている場合)、シェルは対話モードになります。シェルが起動した後は、そのシェルの対話モードのオン・オフを切り替えることはできません。 対話モードが有効な時…… - link:invoke.html#init[シェルの初期化]時に初期化スクリプトを読み込んで実行します。 - コマンドを読み込む際に<>を行い、<>を表示します。{zwsp}link:job.html[ジョブ制御]が有効ならジョブの状態変化も表示します。端末の種類によっては{zwsp}link:lineedit.html[行編集]が使えます。 - 実行したコマンドは自動的に<>に登録されます。 - 実行したコマンドが SIGINT/SIGPIPE 以外のシグナルによって中断されたとき、シェルはそのことを示す警告メッセージを標準エラーに出力します。 - link:redir.html#file[ファイルのリダイレクト]の対象ファイルを指示するトークンに対して{zwsp}link:expand.html#glob[パス名展開]を行います。 - コマンド解釈・実行時に文法エラーや展開エラーが発生してもシェルは終了しません。(link:exec.html#exit[シェルの終了]を参照) - SIGINT, SIGTERM, SIGQUIT シグナルを受けても、シェルは終了しません。 - シグナル受信時の挙動がシェルの起動時に最初から ``無視'' に設定されていても link:_trap.html[trap 組込みコマンド]でシグナル受信時の挙動を変更できます。 - link:params.html#special[特殊パラメータ] +-+ の値に +i+ が含まれます。 - シェル実行中に link:params.html#sv-lc_ctype[+LC_CTYPE+ 変数]の値が変わった時、それを直ちにシェルのロケール情報に反映します。(link:posix.html[POSIX 準拠モード]を除く) - link:_set.html#so-exec[Exec オプション]が無効な時でもコマンドを実行します。 - link:_set.html#so-ignoreeof[Ignore-eof オプション]が効果を発揮します。 - link:_exit.html[Exit 組込みコマンド]でシェルを終了しようとした時、停止している{zwsp}link:job.html[ジョブ]があれば、シェルは警告を表示してすぐには終了しません。このときは続けざまにもう一度 exit コマンドを実行すると本当にシェルを終了させることができます。シェルへの入力が終わりに達した場合も同様です。 - link:_suspend.html[Suspend 組込みコマンド]でシェルを停止させようとした時、シェルがセッションリーダーならエラーを出力して停止しません。 - link:_dot.html[ドット組込みコマンド]で読み込むスクリプトファイルが見つからなくても、シェルは終了しません。 - link:_exec.html[Exec 組込みコマンド]でコマンドの実行に失敗したときでもシェルは終了しません。(link:posix.html[POSIX 準拠モード]のときを除く) - link:_wait.html[Wait 組込みコマンド]で待っているジョブが終了したとき、そのことを示すメッセージを出力します。(link:job.html[ジョブ制御]が有効な時のみ。{zwsp}link:posix.html[POSIX 準拠モード]を除く) - link:_read.html[Read 組込みコマンド]が二行目以降を読むときプロンプトを出します。 [[prompt]] == プロンプト 対話モードでは、シェルはコマンドの入力を読み取る直前にdfn:[プロンプト]を標準エラーに出力します。プロンプトの内容は link:params.html#sv-ps1[+PS1+ 変数]で指定します。ただし、複数行にわたるコマンドを読み取る際、二行目以降の読み取りには +PS1+ ではなく link:params.html#sv-ps2[+PS2+ 変数]の値がプロンプトとして表示されます。 プロンプトの表示の際には、まず +PS1+ (または +PS2+) 変数の値が{zwsp}link:expand.html#params[パラメータ展開]・{zwsp}link:expand.html#cmdsub[コマンド置換]・{zwsp}link:expand.html#arith[数式展開]で展開されます (ただし POSIX によればパラメータ展開だけが行われることになっています)。この展開後の値は以下の通り解釈され、その結果がプロンプトとして標準エラーに出力されます。 link:posix.html[POSIX 準拠モード]では、値に含まれる +!+ はこれから入力しようとしているコマンドの<>番号に変換されます。感嘆符そのものをプロンプトに表示させるには +!!+ と二つ続けて書きます。これ以外の文字はプロンプトにそのまま表示されます。 POSIX 準拠モードでないときは、バックスラッシュで始まる以下の記法が解釈されます。これらの記法以外の文字はそのままプロンプトに表示されます。 +\a+:: ベル文字 (ASCII コード番号 7) +\e+:: エスケープ文字 (ASCII コード番号 27) +\j+:: 現在シェルが抱えている{zwsp}link:job.html[ジョブ]の数 +\n+:: 改行文字 (ASCII コード番号 10) +\r+:: 復帰文字 (ASCII コード番号 13) +\!+:: これから入力しようとしているコマンドの<>番号 +\$+:: シェルの実効ユーザ ID が 0 のときは ++#++、それ以外の時は ++$++。 +\\+:: バックスラッシュ (+\+) +\[+:: +\]+:: この二つの記法は、実際には端末に表示されないプロンプトの一部分を指示するのに使います。+\[+ と +\]+ で囲んだ部分は、{zwsp}link:lineedit.html[行編集]がプロンプトの文字数を計算する際に、文字数に数えられません。端末に表示されないエスケープシーケンスなどをプロンプトに含める際は、その部分を +\[+ と +\]+ で囲んでください。この指定を怠ると、行編集の表示が乱れることがあります。 +\f{{フォント指定}}.+:: link:lineedit.html[行編集]を使用している場合、この記法は端末のフォントの表示を変更するための特殊な文字の羅列に変換されます (端末が対応している場合のみ)。行編集を使用していない場合や端末が対応していない場合は、この記法は単に無視されます。{{フォント指定}}の部分にはフォントの種類を指定するための以下の文字を指定します。 + -- +k+:: 文字の色を黒にする +r+:: 文字の色を赤にする +g+:: 文字の色を緑にする +y+:: 文字の色を黄にする +b+:: 文字の色を青にする +m+:: 文字の色をマゼンタにする +c+:: 文字の色をシアンにする +w+:: 文字の色を白にする +K+:: 背景の色を黒にする +R+:: 背景の色を赤にする +G+:: 背景の色を緑にする +Y+:: 背景の色を黄にする +B+:: 背景の色を青にする +M+:: 背景の色をマゼンタにする +C+:: 背景の色をシアンにする +W+:: 背景の色を白にする +t+:: 文字または背景の色を明るくする (上記の文字・背景の色を変更する文字の直後でのみ有効) +d+:: 文字と背景の色を標準状態に戻す +s+:: 文字を目立たせる +u+:: 文字に下線を引く +v+:: 文字の色を反転させる +b+:: 文字を点滅させる +i+:: 文字の色を暗くする +o+:: 文字を太く目立たせる +x+:: 文字を見えなくする +D+:: 色と装飾を標準状態に戻す -- + 文字と背景の色は最終的に端末によって決まるため、実際にはここで指定した色と異なる色で表示されることがあります。 入力するコマンドの右側に表示されるプロンプトを指定することもできます (dfn:[右プロンプト])。+PS1+/+PS2+ 変数に対応する右プロンプトは link:params.html#sv-ps1r[+PS1R+]/link:params.html#sv-ps2r[+PS2R+] 変数で指定します。 また、プロンプトのフォントだけでなく、入力するコマンドのフォントを変えることもできます。{zwsp}link:params.html#sv-ps1s[+PS1S+] (または link:params.html#sv-ps2s[+PS2S+]) 変数に上述のフォントを指定するシーケンスを指定することで、コマンド入力時のコマンドのフォントが変わります。 link:posix.html[POSIX 準拠モード]でないときは、上記の変数は名前に +YASH_+ を付けた名前 (例えば link:params.html#sv-yash_ps1[+YASH_PS1+]) で定義することもできます。これにより、POSIX 準拠モードとは異なるプロンプトを使い分けることができます。 link:posix.html[POSIX 準拠モード]でないときは、プロンプトを出す前に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]の値がコマンドとして実行されます。 [[history]] == コマンド履歴 dfn:[コマンド履歴]は実行したコマンドを記録し後で再び実行することのできる機能です。対話モードでシェルが読み込んだコマンドは自動的にコマンド履歴に記録されます。履歴に記録したコマンドは{zwsp}link:lineedit.html[行編集]で呼び出して再実行することができます。また link:_fc.html[fc]・{zwsp}link:_history.html[history] 組込みコマンドで履歴のコマンドを再実行したり編集したりすることもできます。 コマンドは行単位で履歴に記録されます。空白以外の文字を一切含まない行は履歴には記録されません。また link:_set.html#so-histspace[hist-space オプション]が有効なときは空白で始まる行は履歴に記録されません。 コマンド履歴の内容は link:params.html#sv-histfile[+HISTFILE+ 変数]で指定されるファイルに保存されます。対話モードのシェルの起動後に履歴関連の機能が初めて使用されるとき、+HISTFILE+ 変数の値をファイル名とみなしてファイルを開きます。既にファイルに履歴データが保存されている場合は、それが読み込まれます。ファイルが存在しないか内容が履歴データではない場合は、新しい履歴ファイルに初期化されます。+HISTFILE+ 変数が存在しない場合やファイルを開くことができない場合は履歴はファイルに保存されませんが、履歴機能自体は使用できます。 シェルが記録するコマンドの数は link:params.html#sv-histsize[+HISTSIZE+ 変数]で指定します。履歴の件数がこの変数の値を超えると順次古いデータから削除されます。この変数が存在しない場合または値が自然数でない場合は、履歴は 500 件まで記録されます。 +HISTFILE+ および +HISTSIZE+ 変数は履歴機能が初めて使用されるときにのみ参照され、それ以降は変数を再設定しても履歴機能の動作に影響しません。履歴機能が利用されるときというのは、具体的には以下のタイミングです。 - link:_fc.html[Fc] または link:_history.html[history] 組込みコマンドを実行したとき - link:lineedit.html[行編集]を使用してコマンドを入力するとき (履歴データを行編集の中で使わなくても履歴機能は使われます) - 入力したコマンドが履歴に登録されるとき このため +HISTFILE+ および +HISTSIZE+ 変数は原則として{zwsp}link:invoke.html#init[シェルの起動時]に読み込まれる初期化スクリプトの中で設定する必要があります。 複数のシェルプロセスが同じ履歴ファイルを使用している場合、これらのシェルは一つの履歴データを共有します。このとき例えばあるシェルプロセスで実行したコマンドを別のシェルプロセスで実行することができます。同じ履歴を使用しているシェルの間で +HISTSIZE+ が異なっていると履歴が正しく共有されないので、+HISTSIZE+ の値は統一するようにしてください。 Yash は独自の形式の履歴ファイルを使用しているため、履歴ファイルを他の種類のシェルと共用することはできません。 履歴に同じコマンドを記録する無駄を解消するため、{zwsp}link:params.html#sv-histrmdup[+HISTRMDUP+ 変数]を使用することができます。新しくコマンドを履歴に記録しようとする際、すでに同じコマンドが最近の {{$HISTRMDUP}} 件の履歴データの中に記録されていれば、その既に記録されているコマンドは履歴から削除されます。 [[mailcheck]] == メールチェック 対話モードのシェルには、電子メールが届いたらそれを知らせる機能があります。これは所定のファイルの更新日時を調べて、更新日時が変わっていたらメッセージを表示するというものです。受信したメールのデータが保存されるファイルをチェック対象として指定しておくことで、メールを受信したときにメッセージが表示されるようになります。 ファイル更新のチェックはシェルがプロンプトを出す直前に行います。チェックを行う間隔を link:params.html#sv-mailcheck[+MAILCHECK+ 変数]で指定することができます。この変数で指定した秒数が経過するごとに、シェルはプロンプトを出す直前にチェックを行います。この変数の値が 0 になっている場合は、プロンプトを出す直前に毎回チェックを行います。また変数の値が 0 以上の整数でない場合は、チェックは一切行いません。 更新日時をチェックする対象のファイルは link:params.html#sv-mail[+MAIL+ 変数]で指定します。この変数にチェックしたいファイルのパス名を指定しておくと、シェルはそのファイルの更新日時をチェックします。ファイルの更新日時が前回チェックしたときと変わっていたら、新着メールを知らせるメッセージを標準エラーに出力します。(ただしファイルが空のときはメッセージは出ません (link:posix.html[POSIX 準拠モード]のときを除く)) 複数のファイルをチェックの対象にしたい場合やメッセージを自分で指定したい場合は、+MAIL+ 変数の代わりに link:params.html#sv-mailpath[+MAILPATH+ 変数]を使うことができます。+MAILPATH+ 変数が設定されている場合は、+MAIL+ 変数の設定は無視されます。+MAILPATH+ 変数の値には、一つ以上のファイルのパス名をコロン (+:+) で区切って指定することができます。シェルは毎回のチェックでそれぞれのファイルの更新日時を調べ、ファイルが更新されていたらメッセージを表示します。メッセージを自分で指定するには、パス名の直後にパーセント (+%+) を置き、続けて表示させたいメッセージを置きます。それぞれのファイルごとに異なるメッセージを指定することができます。(パーセントをパス名とメッセージとの区切りではなくパス名の一部としたい場合はパーセントをバックスラッシュで{zwsp}link:syntax.html#quotes[クォート]してください) パーセントの後に指定されたメッセージは、表示の前に{zwsp}link:expand.html#params[パラメータ展開]されます。 例えば +MAILPATH+ 変数の値が `/foo/mail%New mail!:/bar/mailbox%You've got mail:/baz/mail\%data` だとすると、ファイル /foo/mail が更新されたときは `New mail!` が、/bar/mailbox が更新されたときは `You've got mail` が、/baz/mail%data が更新されたときはデフォルトのメッセージが表示されます。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/intro.txt000066400000000000000000000026401354143602500154540ustar00rootroot00000000000000= はじめに :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - はじめに :description: Yash マニュアルの導入ページ dfn:[Yet another shell] (yash) は Unix 系 OS 用コマンドラインシェルです。POSIX.1-2008 規格に (一部の例外を除いて) 準拠しています。POSIX 準拠を謳う他のシェルよりも精確な準拠を目指しています。また、コマンド履歴やコマンドライン編集など、対話シェルとして標準的な機能も備えています。 このプログラムは http://www.gnu.org/licenses/gpl.html[GNU General Public License] (Version 2) の元で自由に再配布・変更などができます。**ただし、プログラムの利用は全て各自の自己責任の下で行っていただくことになります。**作者はプログラムの瑕疵に対して一切責任を取りません。また、このマニュアルはhttp://creativecommons.org/licenses/by-sa/2.1/jp/[クリエイティブ・コモンズの表示-継承 2.1 日本ライセンス]の下で自由に再配布・変更などができます。 Yash は渡邊裕貴 (通称まじかんと) という一人の開発者によって開発されています。Yash のhttp://osdn.jp/projects/yash/[開発プロジェクト]および http://yash.osdn.jp/[ホームページ]は http://osdn.jp/[OSDN] がホストしています。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/invoke.txt000066400000000000000000000160101354143602500156100ustar00rootroot00000000000000= シェルの起動 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - シェルの起動 :description: Yash の起動時の初期化処理について Yash がプログラムとして起動されると、yash はいくつかの初期化処理を行った後、コマンドを読み取って実行する処理に移ります。これらの処理の内容は、主に起動時のコマンドライン引数によって決まります。 [[arguments]] == 起動時のコマンドライン引数 Yash の起動時のコマンドライン引数は POSIX に準拠しています。POSIX で定められているとおり、引数は dfn:[オプション] と dfn:[オペランド] に分類されます。引数の書式に関する一般的な説明については{zwsp}link:builtin.html#argsyntax[コマンドの引数の構文]を参照してください。オプションはオペランドより前に指定する必要があります。 オプションに +-c+ (+--cmdline+) オプションが含まれている場合、オペランドを少なくとも一つ与える必要があります。シェルは、この最初のオペランドをコマンドとして解釈・実行します。二つ目のオペランドがある場合は、{zwsp}link:params.html#sp-zero[特殊パラメータ +0+] がそれに初期化されます。三つ目以降のオペランドは、{zwsp}link:params.html#positional[位置パラメータ]になります。 +-c+ (+--cmdline+) オプションを指定した場合は、ファイルや標準入力からコマンドを読み込むことはありません (ドット組込みコマンドを使用したときを除く)。 オプションに +-s+ (+--stdin+) オプションが含まれている場合、シェルは標準入力から一行ずつコマンドを読み取って、解釈・実行します。オペランドはすべて{zwsp}link:params.html#positional[位置パラメータ]の初期化に使われます。{zwsp}link:params.html#sp-zero[特殊パラメータ +0+] はこのシェルが起動されたときに元のプログラムから受け取った最初の引数に初期化されます。 +-c+ (+--cmdline+) オプションも +-s+ (+--stdin+) オプションも指定されなかった場合は、シェルはファイルからコマンドを読み取って解釈・実行します。最初のオペランドが読み込むファイル名と見なされ、特殊パラメータ +0+ の値となります。残りのオペランドは{zwsp}link:params.html#positional[位置パラメータ]になります。オペランドが一つもない場合は、 +-s+ (+--stdin+) オプションを指定したときと同様に標準入力からコマンドを読み込みます。 +-c+ (+--cmdline+) オプションと +-s+ (+--stdin+) オプションを同時に使用することはできません。 +--help+ オプションまたは +-V+ (+--version+) オプションが指定されている場合は、通常の初期化処理やコマンドの解釈・実行は一切行いません。それぞれシェルのコマンドライン引数の簡単な説明とバージョン情報を標準出力に出力した後、そのままシェルは終了します。 +-V+ (+--version+) オプションを +-v+ (+--verbose+) オプションと共に使用すると、シェルで利用可能な機能の一覧も出力されます。 +-i+ (+--interactive+) オプションが指定されている場合、シェルは対話モードになります。逆に `+i` (`++interactive`) オプションが指定されている場合、シェルは対話モードになりません。 +-l+ (+--login+) オプションが指定されている場合、シェルはログインシェルとして動作します。 +--noprofile+, +--norcfile+, +--profile+, +--rcfile+ 各オプションは、シェルの初期化処理の動作を指定します (後述)。 その他のオプションとして、{zwsp}link:_set.html[set 組込みコマンド]で指定可能な各種オプションをシェルの起動時に指定することができます。(`+` で始まるオプションを含む) 最初のオペランドが +-+ であり、かつオプションとオペランドが +--+ で区切られていない場合、そのオペランドは特別に無視されます。 [[init]] == シェルの初期化処理 シェルの初期化処理は以下のように行われます。 . Yash はまず、(コマンドライン引数の前に渡される) それ自身の起動時の名前を解析します。その名前が +-+ で始まる場合は、シェルはログインシェルとして動作します。また名前が +sh+ である場合 (+/bin/sh+ のように +sh+ で終わる場合を含む) は、シェルは link:posix.html[POSIX 準拠モード]になります。 . オペランドが一つもなく、かつ標準入力と標準エラーがどちらも端末ならば、シェルは自動的に{zwsp}link:interact.html[対話モード]になります。ただし `+i` (`++interactive`) オプションが指定されている場合はそちらを優先します。 . 対話モードのシェルでは自動的に{zwsp}link:job.html[ジョブ制御]が有効になります。ただし `+m` (`++monitor`) オプションが指定されている場合はそちらを優先します。 . シェルは以下の初期化スクリプトを読み込んで解釈・実行します。(ただしシェルプロセスの実ユーザ ID と実効ユーザ ID が異なっているか、実グループ ID と実効グループ ID が異なっている場合を除く) .. シェルがログインシェルとして動作している場合は、 +--profile={{ファイル名}}+ オプションで指定したファイルを読み込んで実行します。(ただし +--noprofile+ オプションが指定されているか link:posix.html[POSIX 準拠モード]の場合を除く) + +--profile={{ファイル名}}+ オプションが指定されていない場合は、{zwsp}link:expand.html#tilde[~]/.yash_profile がデフォルトのファイルとして使われます。 .. シェルが対話モードの場合は、 +--rcfile={{ファイル名}}+ オプションで指定したファイルを読み込んで実行します。(ただし +--norcfile+ オプションが指定されている場合を除く) + +--rcfile+ オプションが指定されていない場合は、非 POSIX 準拠モードではファイル link:expand.html#tilde[~]/.yashrc がデフォルトのファイルとして使われます。~/.yashrc が読み込めない場合は、ファイル initialization/default を link:params.html#sv-yash_loadpath[+YASH_LOADPATH+] から探して読み込みます。POSIX 準拠モードでは、{zwsp}link:params.html#sv-env[+ENV+ 環境変数]の値が{zwsp}link:expand.html#params[パラメータ展開]され、その結果をファイル名と見なしてファイルを読み込みます。 [NOTE] Yash は /etc/profile や /etc/yashrc や link:expand.html#tilde[~]/.profile を自動的に読むことはありません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/job.txt000066400000000000000000000164311354143602500150760ustar00rootroot00000000000000= ジョブ制御 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - ジョブ制御 :description: Yash のジョブ制御の動作について dfn:[ジョブ制御]とは、複数のコマンドを同時に実行し、必要に応じてそれらを中断・再開させる機能です。シェルは、オペレーティングシステムが提供する端末の機能やプロセスグループ管理機構などを用いて、ジョブ制御を実現します。 ジョブ制御が有効な時…… - シェルが起動する各プロセスは、{zwsp}link:syntax.html#pipelines[パイプライン]ごとに共通の一意なプロセスグループに属します。すなわち、シェルが起動するコマンドはそれぞれパイプラインごとにdfn:[ジョブ]として扱われます。 - シェルがジョブを起動しそのジョブのプロセスが終了するのを待っている間にそのプロセスが停止した場合、シェルは (プロセスが実際に終了したときと同様に) 次のコマンドの処理に移ります。このときシェルはジョブが停止したことを覚えているので、後でジョブを再開させることができます。 - ジョブが{zwsp}link:syntax.html#async[同期的]に実行される場合、そのジョブの実行中はそのジョブのプロセスグループが端末のフォアグラウンドプロセスグループになります。ジョブの実行が終了 (または停止) すると、再びシェルがフォアグラウンドになります。 - link:expand.html#cmdsub[コマンド置換]のコマンドを実行する{zwsp}link:exec.html#subshell[サブシェル]もまた独立したプロセスグループに属します。しかしシェルはこれをジョブとしては扱わないため、停止・再開させることはできません。 - シェルが{zwsp}link:interact.html[対話モード]の場合、プロンプトを出す前に毎回コマンド +link:_jobs.html[jobs] -n+ を実行するのと同様にしてジョブの状態変化を報告します。 - link:syntax.html#async[非同期コマンド]の標準入力が自動的に /dev/null にリダイレクトされません。 - SIGTSTP シグナルを受けても、シェルは停止しません。 - link:params.html#sp-hyphen[特殊パラメータ +-+] の値に +m+ が含まれます。 - link:_wait.html[Wait 組込みコマンド]で待っているジョブが終了したとき、そのことを示すメッセージを出力します。(link:interact.html[対話モード]の時のみ。{zwsp}link:posix.html[POSIX 準拠モード]を除く) ジョブ制御が無効な時、シェルが起動する各プロセスはシェルと同じプロセスグループに属しますが、実行した{zwsp}link:syntax.html#async[非同期コマンド]はそれぞれジョブ制御の対象となっていないジョブとして扱います。 ここでジョブ制御に関連する組込みコマンドを簡単に紹介します。 link:_jobs.html[jobs]:: 現在シェルが管理しているジョブを表示します。 link:_fg.html[fg] および link:_bg.html[bg]:: ジョブをフォアグラウンドまたはバックグラウンドで実行します。主に停止したジョブを再開させるのに使います。 link:_wait.html[wait]:: ジョブが終了 (または停止) するまで待ちます。 link:_disown.html[disown]:: ジョブの存在を忘れます。 link:_kill.html[kill]:: プロセスにシグナルを送ります。 対話モードでジョブ制御が有効な時、シェルはプロンプトを出す直前にジョブの状態変化を報告します。これ以外のタイミングで状態変化を報告してほしい場合は、以下の{zwsp}link:_set.html#options[オプション]を指定することができます。 link:_set.html#so-notify[notify]:: タイミングにかかわらず、ジョブの状態が変化したら直ちにそれを報告します。 link:_set.html#so-notifyle[notify-le]:: link:lineedit.html[行編集]を行っている最中にジョブの状態が変化したら直ちにそれを報告します。 シェルが管理しているジョブは以下のタイミングで削除されます。 - ジョブの実行が終了した後、そのことを link:_jobs.html[jobs 組込みコマンド]で表示したとき - link:_wait.html[Wait 組込みコマンド]でジョブの終了を待ったとき - link:_disown.html[Disown 組込みコマンド]でジョブを削除したとき 対話シェルが自動的にジョブの状態変化を報告するときにはジョブは削除されません。 [[jobid]] == ジョブ ID いくつかの{zwsp}link:builtin.html[組込みコマンド]では、操作対象のジョブを指定するためにdfn:[ジョブ ID] という以下のような記法を用います。 +%+:: +%%+:: +%++:: 現在のジョブ +%-+:: 前のジョブ +%{{n}}+:: ジョブ番号が {{n}} のジョブ ({{n}} は自然数) +%{{string}}+:: ジョブ名が{{文字列}}で始まるジョブ +%?{{string}}+:: ジョブ名が{{文字列}}を含むジョブ dfn:[現在のジョブ]及びdfn:[前のジョブ]とは、シェルが特定の方法で選んだジョブのことで、{zwsp}link:_fg.html[fg 組込みコマンド]などでジョブを選択しやすくするために用意されています。現在のジョブと前のジョブは以下の規則を満たすように選ばれます。 - 停止中のジョブがある場合は、現在のジョブはその中から選ばれます。 - 現在のジョブ以外に停止中のジョブがある場合は、前のジョブはその中から選ばれます。 - 現在のジョブと前のジョブは異なるジョブになるように選ばれます。ジョブが一つしかないときはそれが現在のジョブになり、前のジョブはなくなります。 - 現在のジョブが終了したときは、前のジョブが現在のジョブになります。これ以外に現在のジョブが変更される場合は、元の現在のジョブは前のジョブになります。 - フォアグラウンドで実行していたジョブが停止したときは、そのジョブは現在のジョブになります。 Yash には、現在のジョブを選択する方針を指示するためにいくつかの{zwsp}link:_set.html#options[オプション]が用意されています。ただしこれらのオプションよりも上記の規則のほうが優先します。 link:_set.html#so-curasync[cur-async]:: 新しく{zwsp}link:syntax.html#async[非同期コマンド]を起動したとき、それは現在のジョブになります。 link:_set.html#so-curbg[cur-bg]:: link:_bg.html[Bg 組込みコマンド]でジョブを再開したとき、そのジョブは現在のジョブになります。 link:_set.html#so-curstop[cur-stop]:: 実行中のジョブが停止したとき、そのジョブは現在のジョブになります。 これらの規則・オプションに反しない限り、一度選ばれた現在のジョブ・前のジョブはずっと現在のジョブ・前のジョブのままです。 POSIX は現在のジョブ・前のジョブの選択方法を細かく定めていないため、他のシェルでは選び方が異なることがあります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/lineedit.txt000066400000000000000000001702731354143602500161260ustar00rootroot00000000000000= 行編集 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - 行編集 :description: Yash の行編集機能の説明 dfn:[行編集]機能は、{zwsp}link:interact.html[対話モード]のシェルにコマンドを入力する際に使える、コマンドの簡易編集機能です。行編集機能は、コマンドを編集するための簡単なエディタとして働きます。行編集機能は{zwsp}link:interact.html#history[コマンド履歴]とも連携しており、{zwsp}link:_fc.html[fc コマンド]を使ってエディタを起動する代わりに行編集で直接コマンドを編集・再実行できます。 行編集には複数の編集モードがあり、モードごとにキー操作の割り当てが異なります。行編集の有効・無効を切り替えたりモードを選択したりするには、{zwsp}link:_set.html[set 組込みコマンド]で編集モードに対応するオプションを設定します。あるモードに対応するオプションを有効にすると、そのモードの行編集が有効になります (同時に他のモードのオプションは自動的に無効になります)。現在有効になっているモードのオプションを無効にすると、行編集は無効になります。現在 yash が搭載している編集モードは vi 風と emacs 風の二種類で、それぞれ対応するオプションは +-o vi+ と +-o emacs+ です。 シェルが対話モードで起動したとき、標準入力と標準エラーがともに端末ならば、vi 風行編集が自動的に有効になります。 行編集は、標準入力と標準エラーがともに端末のときだけ使えます。この条件が満たされていないときは、行編集は働きません。行編集が働くとき、シェルは termios インタフェースを使用して端末の入出力モードを一時的に変更し、terminfo インタフェースを使用して入力されたキーの判別などを行います。 [[options]] == 行編集のオプション 行編集を有効にし編集モードを選択するオプションとして、以下のオプションが link:_set.html[set 組込みコマンド]で設定できます。 link:_set.html#so-vi[vi]:: Vi 風編集モードを有効にします link:_set.html#so-emacs[emacs]:: Emacs 風編集モードを有効にします この他に、行編集に関わるものとして以下のオプションが設定できます。 link:_set.html#so-lealwaysrp[le-always-rp]:: このオプションが無効な時は、長いコマンドを入力してコマンドが右プロンプトに達すると、右プロンプトは見えなくなります。このオプションが有効な時は、右プロンプトは見えなくなる代わりに下に移動します。 link:_set.html#so-lecompdebug[le-comp-debug]:: <>を行う際にデバッグ用の情報を出力します link:_set.html#so-leconvmeta[le-conv-meta]:: Terminfo データベースで得られた情報を無視し、入力の 8 ビット目を常に meta-key フラグとみなします。 link:_set.html#so-lenoconvmeta[le-no-conv-meta]:: Terminfo データベースで得られた情報を無視し、入力の 8 ビット目を他のビットと同様に扱います。 + Le-conv-meta オプションと le-no-conv-meta オプションは片方しか有効にできません (片方を有効にするともう片方は自動的に無効になります)。どちらも無効な時は terminfo データベースの情報に従って 8 ビット目を meta-key とみなすかどうか判断します。 link:_set.html#so-lepredict[le-predict]:: <>を有効にします link:_set.html#so-lepredictempty[le-predict-empty]:: <>とこのオプションが有効な時、まだ何も入力されていないコマンドラインでも推定結果を表示します。 link:_set.html#so-lepromptsp[le-prompt-sp]:: このオプションが有効な時、シェルは{zwsp}link:interact.html#prompt[プロンプト]を出力する前に、プロンプトが必ず行頭に来るようにカーソルを移動するための特殊な文字列を出力します。 + このオプションは最初から有効になっています。 link:_set.html#so-levisiblebell[le-visible-bell]:: シェルが警告を発する際、警告音を鳴らす代わりに端末を点滅させます。 [[modes]] == 編集モード dfn:[Vi 風編集モード]は vi に似たキー操作で編集を行う編集モードです。Vi 風編集モードでは、挿入モードとコマンドモードの二つのモードを適宜切り替えて編集を行います。編集が始まるときはモードは必ず挿入モードになっています。挿入モードでは入力した文字が基本的にそのままバッファに挿入されます。コマンドモードでは入力した文字はカーソルを移動したり文字を消去したりするコマンドとして解釈されます。 dfn:[Emacs 風編集モード]は emacs に似たキー操作で編集を行う編集モードです。入力した文字は基本的にそのままバッファに挿入されますが、コマンドとして解釈される一部のキー操作が vi 風編集モードの挿入モードと異なります。 これらのモードの他に、コマンドの検索の際に用いる検索モードが vi 風と emacs 風とそれぞれにあります。 [[commands]] == 行編集コマンド 行編集中に入力された文字は全て以下の行編集コマンドのいずれかとして解釈されます。コマンドとキーの対応は link:_bindkey.html[bindkey 組込みコマンド]で変更できます (検索モードを除く)。 以下の一覧には各コマンドに対応するキー入力の初期設定も示してあります。なお、 ``vi-insert'' は vi 風編集モードの挿入モードを、 ``vi-command'' は vi 風編集モードのコマンドモードを、 ``vi-search'' は vi 風編集モードの検索モードを、 ``emacs'' は emacs 風編集モードを、 ``emacs-search'' は emacs 風編集モードの検索モードを示します。 コマンドの中には引数を指定することでその動作を変更できるものがあります。例えば forward-char コマンドは通常はカーソルを一文字分前に移動しますが、引数を指定するとその引数の文字数分だけカーソルを移動します。引数は、目的のコマンドの直前に digit-argument コマンドを使うことで指定できます。 [[basic-commands]] === 基本的な編集コマンド noop:: 何も行いません。 + -- vi-command:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] -- alert:: 警告音を発しまたは端末を点滅させます。 self-insert:: 入力した文字を現在のカーソルの位置に挿入します。(<>によるエスケープの対象となる文字は挿入できません) + <>が有効な場合、推定された部分は無視します。Accept-prediction コマンドも参照。 + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- insert-tab:: タブを現在のカーソルの位置に挿入します。 + -- emacs:: ifdef::basebackend-html[+++\^[\^I+++] ifndef::basebackend-html[`\^[\^I`] -- expect-verbatim:: このコマンドの直後に入力する一文字を現在のカーソル位置に挿入します。このコマンドを使うと self-insert コマンドで入力できない文字も入力できます (ナル文字 `'\0'` を除く)。 + -- vi-insert:: vi-search:: emacs-search:: ifdef::basebackend-html[+++\^V+++] ifndef::basebackend-html[`\^V`] emacs:: ifdef::basebackend-html[] +++\^Q, \^V+++ endif::basebackend-html[] ifndef::basebackend-html[`\^Q`, `\^V`] -- digit-argument:: このコマンドは数字またはハイフンの入力に対してのみ有効です。入力した数字を次のコマンドへの引数として受け付けます (ハイフンの場合は符号を反転します)。 + Digit-argument コマンドを連続して使うことで複数桁の引数を指定できます。例えば vi 風編集モードのコマンドモードで +12l+ と入力すると、forward-char コマンドに引数 12 を与えたことになります (すなわちカーソルが左に 12 文字分移動します)。 + -- vi-command:: ifdef::basebackend-html[] +++1, 2, 3, 4, 5, 6, 7, 8, 9+++ endif::basebackend-html[] ifndef::basebackend-html[`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`] emacs:: ifdef::basebackend-html[] +++\^[0, \^[1, \^[2, \^[3, \^[4, \^[5, \^[6, \^[7, \^[8, \^[9, \^[-+++ endif::basebackend-html[] ifndef::basebackend-html[] `\^[0`, `\^[1`, `\^[2`, `\^[3`, `\^[4`, `\^[5`, `\^[6`, `\^[7`, `\^[8`, `\^[9`, `\^[-`, endif::basebackend-html[] -- bol-or-digit:: 引数がない場合は beginning-of-line コマンドと同じように、引数がある場合は digit-argument コマンドと同じように動作します。 + -- vi-command:: ifdef::basebackend-html[+++0+++] ifndef::basebackend-html[`0`] -- accept-line:: 行編集を終了し、現在のバッファの内容をシェルへの入力として与えます。行の末尾には自動的に改行が付け加わります。 + -- vi-insert:: vi-command:: emacs:: emacs-search:: ifdef::basebackend-html[] +++\^J, \^M+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^M`] -- abort-line:: 行編集を中止し、空の入力をシェルに与えます。 + -- vi-insert:: vi-command:: vi-search:: emacs:: emacs-search:: ifdef::basebackend-html[] +++\!, \^C+++ endif::basebackend-html[] ifndef::basebackend-html[`\!`, `\^C`] -- eof:: シェルに入力の終わりを知らせます (これによりシェルは終了します)。 eof-if-empty:: バッファが空ならば、行編集を終了し、シェルに入力の終わりを知らせます (これによりシェルは終了します)。バッファが空でなければ、alert コマンドと同じ動作をします。 + -- vi-insert:: vi-command:: ifdef::basebackend-html[] +++\#, \^D+++ endif::basebackend-html[] ifndef::basebackend-html[`\#`, `\^D`] -- eof-or-delete:: バッファが空ならば、行編集を終了し、シェルに入力の終わりを知らせます (これによりシェルは終了します)。バッファが空でなければ、delete-char コマンドと同じ動作をします。 + -- emacs:: ifdef::basebackend-html[] +++\#, \^D+++ endif::basebackend-html[] ifndef::basebackend-html[`\#`, `\^D`] -- accept-with-hash:: 引数が与えられていないかバッファの最初の文字が +#+ でなければ、バッファの最初に +#+ を挿入します。そうでなければバッファの最初の +#+ を削除します。いずれの場合も、その後 accept-line コマンドと同じ動作をします。 + -- vi-command:: ifdef::basebackend-html[+++#+++] ifndef::basebackend-html[`#`] emacs:: ifdef::basebackend-html[+++\^[#+++] ifndef::basebackend-html[`\^[#`] -- accept-prediction:: Accept-line コマンドと同様に行編集を終了しますが、<>された部分も含めたコマンドを入力として扱います。 setmode-viinsert:: 編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++i, \I+++] ifndef::basebackend-html[`i`, `\I`] -- setmode-vicommand:: 編集モードを vi 風編集モードのコマンドモードに変更します。 + -- vi-insert:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] -- setmode-emacs:: 編集モードを emacs 風編集モードに変更します。 expect-char:: abort-expect-char:: これは find-char コマンドなどを実装するために yash 内部で使われているコマンドで、直接使用しても意味はありません。 redraw-all:: 行編集のプロンプトやバッファを端末に表示しなおします。 + -- vi-insert:: vi-command:: vi-search:: emacs:: emacs-search:: ifdef::basebackend-html[+++\^L+++] ifndef::basebackend-html[`\^L`] -- clear-and-redraw-all:: 端末の表示をクリアし、行編集のプロンプトやバッファを端末に表示しなおします。 [[motion-commands]] === 移動コマンド dfn:[移動コマンド]はカーソルを移動させるコマンドです。ほとんどの移動コマンドは引数を与えることでそのコマンドを引数の回数だけ実行するのと同じように動作させられます。例えば forward-char コマンドに引数 4 を与えると、カーソルを 4 文字先に進めます。 以下、dfn:[bigword] とは一文字以上の連続した空白でない文字をいい、dfn:[semiword] とは一文字以上の連続した空白でも句読点でもない文字をいい、dfn:[emacsword] とは一文字以上の連続した英数字をいいます。また dfn:[viword] とは以下のいずれかをいいます - 一文字以上の連続した英数字または下線 (+_+) - 一文字以上の連続した、英数字でも下線でも空白でもない文字 以下に移動コマンドの一覧を示します。 forward-char:: カーソルを次の文字に移動します。 + -- vi-insert:: ifdef::basebackend-html[+++\R+++] ifndef::basebackend-html[`\R`] vi-command:: ifdef::basebackend-html[] +++l, (空白文字), \R+++ endif::basebackend-html[] ifndef::basebackend-html[`l`, (space), `\R`] emacs:: ifdef::basebackend-html[] +++\R, \^F+++ endif::basebackend-html[] ifndef::basebackend-html[`\R`, `\^F`] -- backward-char:: カーソルを前の文字に移動します。 + -- vi-insert:: ifdef::basebackend-html[+++\L+++] ifndef::basebackend-html[`\L`] vi-command:: ifdef::basebackend-html[] +++h, \B, \L, \?, \^H, +++ endif::basebackend-html[] ifndef::basebackend-html[`h`, `\B`, `\L`, `\?`, `\^H`] emacs:: ifdef::basebackend-html[] +++\L, \^B+++ endif::basebackend-html[] ifndef::basebackend-html[`\L`, `\^B`] -- forward-bigword:: カーソルを次の bigword に移動します。 + -- vi-command:: ifdef::basebackend-html[+++W+++] ifndef::basebackend-html[`W`] -- end-of-bigword:: カーソルを bigword の終わりまで移動します。 + -- vi-command:: ifdef::basebackend-html[+++E+++] ifndef::basebackend-html[`E`] -- backward-bigword:: カーソルを前の bigword に移動します。 + -- vi-command:: ifdef::basebackend-html[+++B+++] ifndef::basebackend-html[`B`] -- forward-semiword:: カーソルを次の semiword に移動します。 end-of-semiword:: カーソルを semiword の終わりまで移動します。 backward-semiword:: カーソルを前の semiword に移動します。 forward-viword:: カーソルを次の viword に移動します。 + -- vi-command:: ifdef::basebackend-html[+++w+++] ifndef::basebackend-html[`w`] -- end-of-viword:: カーソルを viword の終わりまで移動します。 + -- vi-command:: ifdef::basebackend-html[+++e+++] ifndef::basebackend-html[`e`] -- backward-viword:: カーソルを前の viword に移動します。 + -- vi-command:: ifdef::basebackend-html[+++b+++] ifndef::basebackend-html[`b`] -- forward-emacsword:: カーソルを次の emacsword に移動します。 + -- emacs:: ifdef::basebackend-html[] +++\^[f, \^[F+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[f`, `\^[F`] -- backward-emacsword:: カーソルを前の emacsword に移動します。 + -- emacs:: ifdef::basebackend-html[] +++\^[b, \^[B+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[b`, `\^[B`] -- beginning-of-line:: カーソルをバッファの先頭に移動します。 + -- vi-insert:: vi-command:: ifdef::basebackend-html[+++\H+++] ifndef::basebackend-html[`\H`] emacs:: ifdef::basebackend-html[] +++\H, \^A+++ endif::basebackend-html[] ifndef::basebackend-html[`\H`, `\^A`] -- end-of-line:: カーソルをバッファの末尾に移動します。 + -- vi-insert:: ifdef::basebackend-html[+++\E+++] ifndef::basebackend-html[`\E`] vi-command:: ifdef::basebackend-html[+++$, \E+++] ifndef::basebackend-html[`$`, `\E`] emacs:: ifdef::basebackend-html[] +++\E, \^E+++ endif::basebackend-html[] ifndef::basebackend-html[`\E`, `\^E`] -- go-to-column:: カーソルをバッファ内の {{n}} 文字目に移動します。ただし {{n}} は引数です (引数が与えられていない場合は 1)。 + -- vi-command:: ifdef::basebackend-html[+++|+++] ifndef::basebackend-html[`|`] -- first-nonblank:: カーソルをバッファ内の最初の空白でない文字に移動します。 + -- vi-command:: ifdef::basebackend-html[+++^+++] ifndef::basebackend-html[`^`] -- find-char:: このコマンドの直後に入力した文字がある位置までカーソルを進めます。 + -- vi-command:: ifdef::basebackend-html[+++f+++] ifndef::basebackend-html[`f`] emacs:: ifdef::basebackend-html[+++\^\]+++] ifndef::basebackend-html[`\^\]`] -- find-char-rev:: このコマンドの直後に入力した文字がある位置までカーソルを戻します。 + -- vi-command:: ifdef::basebackend-html[+++F+++] ifndef::basebackend-html[`F`] emacs:: ifdef::basebackend-html[+++\^[\^\]+++] ifndef::basebackend-html[`\^[\^\]`] -- till-char:: このコマンドの直後に入力した文字がある位置の直前までカーソルを進めます。 + -- vi-command:: ifdef::basebackend-html[+++t+++] ifndef::basebackend-html[`t`] -- till-char-rev:: このコマンドの直後に入力した文字がある位置の直後までカーソルを戻します。 + -- vi-command:: ifdef::basebackend-html[+++T+++] ifndef::basebackend-html[`T`] -- refind-char:: 前回実行した find-char, find-char-rev, till-char, till-char-rev コマンドを再実行します。 + -- vi-command:: ifdef::basebackend-html[+++;+++] ifndef::basebackend-html[`;`] -- refind-char-rev:: 前回実行した find-char, find-char-rev, till-char, till-char-rev コマンドを、カーソルの進む向きを逆にして再実行します。 + -- vi-command:: ifdef::basebackend-html[+++,+++] ifndef::basebackend-html[`,`] -- [[editing-commands]] === 編集コマンド 編集コマンドはバッファの内容を変更するコマンドです。ほとんどの編集コマンドは引数を与えることでそのコマンドを引数の回数だけ実行するのと同じように動作させられます。 名前に ``kill'' が付くコマンドで削除した文字列はdfn:[キルリング]という場所に保管され、後で put などのコマンドでバッファに戻すことができます。 以下に編集コマンドの一覧を示します。 delete-char:: カーソルのところにある文字を削除します。引数を与えた場合は kill-char コマンドと同じ動作をします。 + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\X+++] ifndef::basebackend-html[`\X`] -- delete-bigword:: カーソルのところにある bigword を削除します。引数を与えた場合は kill-bigword コマンドと同じ動作をします。 delete-semiword:: カーソルのところにある semiword を削除します。引数を与えた場合は kill-semiword コマンドと同じ動作をします。 delete-viword:: カーソルのところにある viword を削除します。引数を与えた場合は kill-viword コマンドと同じ動作をします。 delete-emacsword:: カーソルのところにある emacsword を削除します。引数を与えた場合は kill-emacsword コマンドと同じ動作をします。 backward-delete-char:: カーソルの前にある文字を削除します。引数を与えた場合は backward-kill-char コマンドと同じ動作をします。 + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\B, \?, \^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\B`, `\?`, `\^H`] -- backward-delete-bigword:: カーソルの前にある bigword を削除します。引数を与えた場合は backward-kill-bigword コマンドと同じ動作をします。 backward-delete-semiword:: カーソルの前にある semiword を削除します。引数を与えた場合は backward-kill-semiword コマンドと同じ動作をします。 + -- vi-insert:: ifdef::basebackend-html[+++\^W+++] ifndef::basebackend-html[`\^W`] -- backward-delete-viword:: カーソルの前にある viword を削除します。引数を与えた場合は backward-kill-viword コマンドと同じ動作をします。 backward-delete-emacsword:: カーソルの前にある emacsword を削除します。引数を与えた場合は backward-kill-emacsword コマンドと同じ動作をします。 delete-line:: バッファの内容を全て削除します。 forward-delete-line:: カーソル以降の全ての文字を削除します。 backward-delete-line:: カーソルより前の全ての文字を削除します。 + -- vi-insert:: ifdef::basebackend-html[] +++\$, \^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`] -- kill-char:: カーソルのところにある文字を削除し、キルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[] +++x, \X+++ endif::basebackend-html[] ifndef::basebackend-html[`x`, `\X`] -- kill-bigword:: カーソルのところにある bigword を削除し、キルリングに保管します。 kill-semiword:: カーソルのところにある semiword を削除し、キルリングに保管します。 kill-viword:: カーソルのところにある viword を削除し、キルリングに保管します。 kill-emacsword:: カーソルのところにある emacsword を削除し、キルリングに保管します。 + -- emacs:: ifdef::basebackend-html[] +++\^[d, \^[D+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[d`, `\^[D`] -- backward-kill-char:: カーソルの前にある文字を削除し、キルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[+++X+++] ifndef::basebackend-html[`X`] -- backward-kill-bigword:: カーソルの前にある bigword を削除し、キルリングに保管します。 + -- emacs:: ifdef::basebackend-html[+++\^W+++] ifndef::basebackend-html[`\^W`] -- backward-kill-semiword:: カーソルの前にある semiword を削除し、キルリングに保管します。 backward-kill-viword:: カーソルの前にある viword を削除し、キルリングに保管します。 backward-kill-emacsword:: カーソルの前にある emacsword を削除し、キルリングに保管します。 + -- emacs:: ifdef::basebackend-html[] +++\^[\B, \^[\?, \^[\^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[\B`, `\^[\?`, `\^[\^H`] -- kill-line:: バッファの内容を全て削除し、キルリングに保管します。 forward-kill-line:: カーソル以降の全ての文字を削除し、キルリングに保管します。 + -- emacs:: ifdef::basebackend-html[+++\^K+++] ifndef::basebackend-html[`\^K`] -- backward-kill-line:: カーソルより前の全ての文字を削除し、キルリングに保管します。 + -- emacs:: ifdef::basebackend-html[] +++\$, \^U, \^X\B, \^X\?+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`, `\^X\B`, `\^X\?`] -- put-before:: 最後にキルリングに保管した文字列をカーソルの直前に挿入します。カーソルは挿入した文字列の最後の文字のところに移動します。 + -- vi-command:: ifdef::basebackend-html[+++P+++] ifndef::basebackend-html[`P`] -- put:: 最後にキルリングに保管した文字列をカーソルの直後に挿入します。カーソルは挿入した文字列の最後の文字のところに移動します。 + -- vi-command:: ifdef::basebackend-html[+++p+++] ifndef::basebackend-html[`p`] -- put-left:: 最後にキルリングに保管した文字列をカーソルの直前に挿入します。カーソルは挿入した文字列の直後に移動します。 + -- emacs:: ifdef::basebackend-html[+++\^Y+++] ifndef::basebackend-html[`\^Y`] -- put-pop:: このコマンドは put-before, put, put-left, put-pop コマンドの直後にだけ使えます。このコマンドは直前のコマンドでキルリングから挿入した文字列を削除し、代わりにその文字列の前にキルリングに保管した文字列を挿入します。 + -- emacs:: ifdef::basebackend-html[] +++\^[y, \^[Y+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[y`, `\^[Y`] -- undo:: 直前の編集コマンドを取り消し、バッファの内容を前の状態に戻します。 + -- vi:: ifdef::basebackend-html[+++u+++] ifndef::basebackend-html[`u`] emacs:: ifdef::basebackend-html[] +++\^_, \^X\$, \^X\^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\^_`, `\^X\$`, `\^X\^U`] -- undo-all:: 全ての編集コマンドを取り消し、バッファの内容を初期状態に戻します。 + -- vi:: ifdef::basebackend-html[+++U+++] ifndef::basebackend-html[`U`] emacs:: ifdef::basebackend-html[] +++\^[\^R, \^[r, \^[R+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[\^R`, `\^[r`, `\^[R`] -- cancel-undo:: undo, undo-all による編集コマンドの取り消しを取り消し、バッファの内容を復元します。 + -- vi:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- cancel-undo-all:: undo, undo-all による編集コマンドの取り消しを全て取り消し、バッファの内容を復元します。 redo:: 直前の編集コマンドを繰り返します。 + -- vi-command:: ifdef::basebackend-html[+++.+++] ifndef::basebackend-html[`.`] -- [[completion-commands]] === 補完コマンド complete:: 現在のカーソル位置で<>を行います。補完候補が複数ある場合はその一覧を表示します。 complete-next-candidate:: 補完候補の一覧を既に表示している場合は一覧の中から次の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\^I+++] ifndef::basebackend-html[`\^I`] -- complete-prev-candidate:: 補完候補の一覧を既に表示している場合は一覧の中から前の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\bt+++] ifndef::basebackend-html[`\bt`] -- complete-next-column:: 補完候補の一覧を既に表示している場合は一覧の中から次の列の最初の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 complete-prev-column:: 補完候補の一覧を既に表示している場合は一覧の中から前の列の最初の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 complete-next-page:: 補完候補の一覧を既に表示している場合は一覧の中から次のページの最初の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 complete-prev-page:: 補完候補の一覧を既に表示している場合は一覧の中から前のページの最初の候補を選択します。それ以外の場合は complete コマンドと同じ動作をします。 complete-list:: 現在のカーソル位置で補完を行います。引数を指定しない場合、補完候補の一覧を表示します。引数を指定すると、その番号の候補で補完内容を確定します。 + -- emacs:: ifdef::basebackend-html[] +++\^[?, \^[=+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[?`, `\^[=`] -- complete-all:: 現在のカーソル位置で補完を行い、カーソル位置にある単語をすべての補完候補で置き換えます。 + -- emacs:: ifdef::basebackend-html[+++\^[*+++] ifndef::basebackend-html[`\^[*`] -- complete-max:: 現在のカーソル位置で補完を行い、各補完候補の最長共通先頭部分をカーソル位置に挿入します。 complete-max-then-list:: 一回目はまず complete-max コマンドと同様に共通部分を挿入します。二回目以降は complete コマンドと同様に候補の一覧を表示します。 complete-max-then-next-candidate:: 一回目はまず complete-max コマンドと同様に共通部分を挿入します。二回目以降は complete-next-candidate コマンドと同様に候補を選択します。 complete-max-then-prev-candidate:: 一回目はまず complete-max コマンドと同様に共通部分を挿入します。二回目以降は complete-prev-candidate コマンドと同様に候補を選択します。 clear-candidates:: 補完候補の一覧を消去します。 [[vi-commands]] === Vi 固有のコマンド vi-replace-char:: カーソルのところにある文字を、このコマンドの直後に入力した文字に置き換えます。 + -- vi-command:: ifdef::basebackend-html[+++r+++] ifndef::basebackend-html[`r`] -- vi-insert-beginning:: カーソルをバッファの先頭に移動したのち、setmode-viinsert コマンドと同じ動作をします。 + -- vi-command:: ifdef::basebackend-html[+++I+++] ifndef::basebackend-html[`I`] -- vi-append:: カーソルを次の文字に移動したのち、setmode-viinsert コマンドと同じ動作をします。 + -- vi-command:: ifdef::basebackend-html[+++I+++] ifndef::basebackend-html[`I`] -- vi-append-to-eol:: カーソルをバッファの末尾に移動したのち、setmode-viinsert コマンドと同じ動作をします。 + -- vi-command:: ifdef::basebackend-html[+++A+++] ifndef::basebackend-html[`A`] -- vi-replace:: Setmode-viinsert コマンドと同じ動作をしますが、同時に上書きモードを有効にします。上書きモードでは、self-insert コマンドは文字を挿入する代わりにカーソルのところにある文字を置き換えます。上書きモードは編集モードを変更するまで有効です。 + -- vi-command:: ifdef::basebackend-html[+++R+++] ifndef::basebackend-html[`R`] -- vi-switch-case:: このコマンドの直後には移動コマンドを入力する必要があります。移動コマンドが動かしたカーソルの範囲にある文字の大文字と小文字を入れ替えます。 vi-switch-case-char:: カーソルのところにある文字の大文字と小文字を入れ替えて、カーソルを次の文字に移動します。 + -- vi-command:: ifdef::basebackend-html[+++~+++] ifndef::basebackend-html[`~`] -- vi-yank:: このコマンドの直後には移動コマンドを入力する必要があります。移動コマンドが動かしたカーソルの範囲にある文字をキルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[+++y+++] ifndef::basebackend-html[`y`] -- vi-yank-to-eol:: カーソルの位置からバッファの末尾までにある文字をキルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[+++Y+++] ifndef::basebackend-html[`Y`] -- vi-delete:: このコマンドの直後には移動コマンドを入力する必要があります。移動コマンドが動かしたカーソルの範囲にある文字を削除し、キルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[+++d+++] ifndef::basebackend-html[`d`] -- vi-delete-to-eol:: カーソルの位置からバッファの末尾までにある文字を削除し、キルリングに保管します。 + -- vi-command:: ifdef::basebackend-html[+++D+++] ifndef::basebackend-html[`D`] -- vi-change:: このコマンドの直後には移動コマンドを入力する必要があります。移動コマンドが動かしたカーソルの範囲にある文字を削除し、その後編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++c+++] ifndef::basebackend-html[`c`] -- vi-change-to-eol:: カーソルの位置からバッファの末尾までにある文字を削除し、その後編集モードを vi 風編集モードの挿入モードに変更します。 Delete the characters from the current cursor position to the end of the line and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++C+++] ifndef::basebackend-html[`C`] -- vi-change-line:: バッファの内容を全て削除し、その後編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++S+++] ifndef::basebackend-html[`S`] -- vi-yank-and-change:: Vi-change コマンドと同様ですが、削除した文字列はキルリングに補完されます。 vi-yank-and-change-to-eol:: Vi-change-to-eol コマンドと同様ですが、削除した文字列はキルリングに補完されます。 vi-yank-and-change-line:: Vi-change-line コマンドと同様ですが、削除した文字列はキルリングに補完されます。 vi-substitute:: カーソルのところにある文字を削除しキルリングに保管した後、編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++s+++] ifndef::basebackend-html[`s`] -- vi-append-last-bigword:: link:interact.html#history[コマンド履歴]の中で最も新しいコマンドにおける最後の bigword を、空白文字に続けてカーソルの位置の直後に挿入します。引数 {{n}} を与えたときは最後の bigword の代わりに {{n}} 番目の bigword を挿入します。その後、setmode-viinsert コマンドと同じ動作をします。 + -- vi-command:: ifdef::basebackend-html[+++_+++] ifndef::basebackend-html[`_`] -- vi-exec-alias:: このコマンドの直後に入力した文字を {{c}} とすると、+_{{c}}+ という名前の{zwsp}link:syntax.html#aliases[エイリアス]の内容をシェルへの入力とみなして行編集コマンドとして解釈・実行します。 + -- vi-command:: ifdef::basebackend-html[+++@+++] ifndef::basebackend-html[`@`] -- vi-edit-and-accept:: エディタとして vi を起動し、バッファの内容を編集させます。エディタが終了すると編集後の内容をバッファに反映した後 accept-line コマンドと同じ動作をします。ただしエディタの終了ステータスが 0 でないときは何も行いません。 + -- vi-command:: ifdef::basebackend-html[+++v+++] ifndef::basebackend-html[`v`] -- vi-complete-list:: Complete-list コマンドと同様ですが、候補を確定したとき編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++=+++] ifndef::basebackend-html[`=`] -- vi-complete-all:: Complete-all コマンドと同様ですが、単語を置き換えた後、編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++*+++] ifndef::basebackend-html[`*`] -- vi-complete-max:: Complete-max コマンドと同様ですが、候補を挿入した後、編集モードを vi 風編集モードの挿入モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- vi-search-forward:: 順方向の{zwsp}link:interact.html#history[履歴]検索を開始します。編集モードを vi 風編集モードの検索モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++?+++] ifndef::basebackend-html[`?`] -- vi-search-backward:: 逆方向の{zwsp}link:interact.html#history[履歴]検索を開始します。編集モードを vi 風編集モードの検索モードに変更します。 + -- vi-command:: ifdef::basebackend-html[+++/+++] ifndef::basebackend-html[`/`] -- [[emacs-commands]] === Emacs 固有のコマンド emacs-transpose-chars:: カーソルの前にある文字を右に移動します。 + -- emacs:: ifdef::basebackend-html[+++\^T+++] ifndef::basebackend-html[`\^T`] -- emacs-transpose-words:: カーソルの前にある emacsword を右に移動します。 + -- emacs:: ifdef::basebackend-html[] +++\^[t, \^[T+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[t`, `\^[T`] -- emacs-downcase-word:: カーソルの後にある emacsword を小文字に変換します。 + -- emacs:: ifdef::basebackend-html[] +++\^[l, \^[L+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[l`, `\^[L`] -- emacs-upcase-word:: カーソルの後にある emacsword を大文字に変換します。 + -- emacs:: ifdef::basebackend-html[] +++\^[u, \^[U+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[u`, `\^[U`] -- emacs-capitalize-word:: カーソルの後にある emacsword をキャピタライズします (各単語の頭文字だけ大文字にする)。 + -- emacs:: ifdef::basebackend-html[] +++\^[c, \^[C+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[c`, `\^[u`] -- emacs-delete-horizontal-space:: カーソルの前後にある空白を削除します。引数を与えるとカーソルの前にある空白を削除します。 + -- emacs:: ifdef::basebackend-html[+++\^[\\+++] ifndef::basebackend-html[`\^[\\`] -- emacs-just-one-space:: カーソルの前後にある空白の個数を一つに調整します。引数を与えるとその引数の数だけ空白を残します。 + -- emacs:: ifdef::basebackend-html[+++\^[ +++] ifndef::basebackend-html[`\^[`] (エスケープの後に空白文字) -- emacs-search-forward:: 順方向の履歴検索を開始します。編集モードを emacs 風編集モードの検索モードに変更します。 + -- emacs:: ifdef::basebackend-html[+++\^S+++] ifndef::basebackend-html[`\^S`] -- emacs-search-backward:: 順方向の履歴検索を開始します。編集モードを emacs 風編集モードの検索モードに変更します。 + -- emacs:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- [[history-commands]] === コマンド履歴関連のコマンド oldest-history:: link:interact.html#history[コマンド履歴]の中で最も古いコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルの位置は変わりません。 newest-history:: link:interact.html#history[コマンド履歴]の中で最も新しいコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルの位置は変わりません。 return-history:: link:interact.html#history[コマンド履歴]のどのコマンドにも対応しない新規バッファに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルの位置は変わりません。 oldest-history-bol:: link:interact.html#history[コマンド履歴]の中で最も古いコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの先頭に移動します。 + -- vi-command:: ifdef::basebackend-html[+++G+++] ifndef::basebackend-html[`G`] -- newest-history-bol:: link:interact.html#history[コマンド履歴]の中で最も新しいコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの先頭に移動します。 return-history-bol:: link:interact.html#history[コマンド履歴]のどのコマンドにも対応しない新規バッファに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの先頭に移動します。 + -- vi-command:: ifdef::basebackend-html[+++g+++] ifndef::basebackend-html[`g`] -- oldest-history-eol:: link:interact.html#history[コマンド履歴]の中で最も古いコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの末尾に移動します。 + -- emacs:: ifdef::basebackend-html[+++\^[<+++] ifndef::basebackend-html[`\^[<`] -- newest-history-eol:: link:interact.html#history[コマンド履歴]の中で最も新しいコマンドに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの末尾に移動します。 return-history-eol:: link:interact.html#history[コマンド履歴]のどのコマンドにも対応しない新規バッファに移動します。引数を与えるとそれを履歴番号とみなしてその番号のコマンドに移動します。カーソルはバッファの末尾に移動します。 + -- emacs:: ifdef::basebackend-html[+++\^[>+++] ifndef::basebackend-html[`\^[>`] -- next-history:: link:interact.html#history[コマンド履歴]の中で次のコマンドに移動します。カーソルの位置は変わりません。 prev-history:: link:interact.html#history[コマンド履歴]の中で前のコマンドに移動します。カーソルの位置は変わりません。 next-history-bol:: link:interact.html#history[コマンド履歴]の中で次のコマンドに移動します。カーソルはバッファの先頭に移動します + -- vi-command:: ifdef::basebackend-html[] +++j, +, \D, \^N+++ endif::basebackend-html[] ifndef::basebackend-html[`j`, `+`, `\D`, `\^N`] -- prev-history-bol:: link:interact.html#history[コマンド履歴]の中で前のコマンドに移動します。カーソルはバッファの先頭に移動します + -- vi-command:: ifdef::basebackend-html[] +++k, -, \U, \^P+++ endif::basebackend-html[] ifndef::basebackend-html[`k`, `-`, `\U`, `\^P`] -- next-history-eol:: link:interact.html#history[コマンド履歴]の中で次のコマンドに移動します。カーソルはバッファの末尾に移動します + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\D, \^N+++ endif::basebackend-html[] ifndef::basebackend-html[`\D`, `\^N`] -- prev-history-eol:: link:interact.html#history[コマンド履歴]の中で前のコマンドに移動します。カーソルはバッファの末尾に移動します + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\U, \^P+++ endif::basebackend-html[] ifndef::basebackend-html[`\U`, `\^P`] -- search-again:: 最後に行ったコマンド履歴検索をもう一度行います。 + -- vi-command:: ifdef::basebackend-html[+++n+++] ifndef::basebackend-html[`n`] -- search-again-rev:: 最後に行ったコマンド履歴検索を方向を逆にしてもう一度行います。 + -- vi-command:: ifdef::basebackend-html[+++N+++] ifndef::basebackend-html[`N`] -- search-again-forward:: 最後に行ったコマンド履歴検索を順方向にもう一度行います。 search-again-backward:: 最後に行ったコマンド履歴検索を逆方向にもう一度行います。 beginning-search-forward:: link:interact.html#history[コマンド履歴]を順方向に検索し、バッファの先頭から現在のカーソル位置までの間にある文字列が同じ次のコマンドに移動します。カーソル位置は変わりません。 beginning-search-backward:: link:interact.html#history[コマンド履歴]を逆方向に検索し、バッファの先頭から現在のカーソル位置までの間にある文字列が同じ前のコマンドに移動します。カーソル位置は変わりません。 [[search-commands]] === コマンド履歴検索モードのコマンド srch-self-insert:: 入力した文字を検索用バッファに挿入します。(<>によるエスケープの対象となる文字は挿入できません) + -- vi-search:: emacs-search:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- srch-backward-delete-char:: 検索用バッファの最後の一文字を削除します。検索用バッファが空の場合は: + -- - vi 風編集モードでは srch-abort-search コマンドと同じ動作をします。 - emacs 風編集モードでは alert コマンドと同じ動作をします。 -- + -- vi-search:: emacs-search:: ifdef::basebackend-html[] +++\B, \?, \^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\B`, `\?`, `\^H`] -- srch-backward-delete-line:: 検索用バッファの内容を全て削除します。 + -- vi-search:: emacs-search:: ifdef::basebackend-html[] +++\$, \^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`] -- srch-continue-forward:: 現在表示している暫定検索結果の次の結果を順方向に探します。 + -- emacs-search:: ifdef::basebackend-html[+++\^S+++] ifndef::basebackend-html[`\^S`] -- srch-continue-backward:: 現在表示している暫定検索結果の次の結果を逆方向に探します。 + -- emacs-search:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- srch-accept-search:: 検索を終了し、現在表示している暫定検索結果を確定します。検索結果に移動します。 + -- vi-search:: ifdef::basebackend-html[] +++\^J, \^M+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^M`] emacs-search:: ifdef::basebackend-html[] +++\^J, \^[+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^[`] -- srch-abort-search:: 検索を中止し、検索を開始する前の状態に戻ります。 + -- vi-search:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] emacs-search:: ifdef::basebackend-html[+++\^G+++] ifndef::basebackend-html[`\^G`] -- [[escape]] == エスケープシーケンス link:_bindkey.html[Bindkey コマンド]で行編集のキー設定を表示・設定する際、ファンクションキーなどの特殊なキーはエスケープシーケンスで表わします。エスケープシーケンスは全てバックスラッシュ (+\+) で始まります。またバックスラッシュそのものもエスケープの対象です。エスケープシーケンスに対するキーの割り当ては以下の通りです。 `\\`:: バックスラッシュ (+\+) `\B`:: Backspace `\D`:: ↓矢印キー `\E`:: End `\H`:: Home `\I`:: Insert (Insert-char, Enter-insert-mode) `\L`:: ←矢印キー `\N`:: Page-down (Next-page) `\P`:: Page-up (Previous-page) `\R`:: →矢印キー `\U`:: ↑矢印キー `\X`:: Delete `\!`:: INTR `\#`:: EOF `\$`:: KILL `\?`:: ERASE `\^@`:: Ctrl + @ `\^A`, `\^B`, ..., `\^Z`:: Ctrl + A, Ctrl + B, ..., Ctrl + Z + ※ Ctrl + I は Tab、Ctrl + J は Newline、Ctrl + M は Carriage-return です。 `\^[`:: Ctrl + [ (Escape) `\^\`:: Ctrl + \ `\^]`:: Ctrl + ] `\^^`:: Ctrl + ^ `\^_`:: Ctrl + _ `\^?`:: Ctrl + ? (Delete) `\F00`, `\F01`, ..., `\F63`:: F0, F1, ..., F63 `\a1`:: キーパッドの左上キー `\a3`:: キーパッドの右上キー `\b2`:: キーパッドの中央キー `\bg`:: Beginning `\bt`:: Back-tab `\c1`:: キーパッドの左下キー `\c3`:: キーパッドの右下キー `\ca`:: Clear-all-tabs `\cl`:: Close `\cn`:: Cancel `\co`:: Command `\cp`:: Copy `\cr`:: Create `\cs`:: Clear-screen または erase `\ct`:: Clear-tab `\dl`:: Delete-line `\ei`:: Exit-insert-mode `\el`:: Clear-to-end-of-line `\es`:: Clear-to-end-of-screen `\et`:: Enter (Send) `\ex`:: Exit `\fd`:: Find `\hp`:: Help `\il`:: Insert-line `\ll`:: Home-down `\me`:: Message `\mk`:: Mark `\ms`:: マウスイベント `\mv`:: Move `\nx`:: Next-object `\on`:: Open `\op`:: Options `\pr`:: Print (Copy) `\pv`:: Previous-object `\rd`:: Redo `\re`:: Resume `\rf`:: Ref (Reference) `\rh`:: Refresh `\rp`:: Replace `\rs`:: Restart `\sf`:: Scroll-forward (Scroll-down) `\sl`:: Select `\sr`:: Scroll-backward (Scroll-up) `\st`:: Set-tab `\su`:: Suspend `\sv`:: Save `\ud`:: Undo `\SE`:: Shift + End `\SH`:: Shift + Home `\SI`:: Shift + Insert `\SL`:: Shift + ←矢印キー `\SR`:: Shift + →矢印キー `\SX`:: Shift + Delete `\Sbg`:: Shift + Beginning `\Scn`:: Shift + Cancel `\Sco`:: Shift + Command `\Scp`:: Shift + Copy `\Scr`:: Shift + Create `\Sdl`:: Shift + Delete-line `\Sel`:: Shift + End-of-line `\Sex`:: Shift + Exit `\Sfd`:: Shift + Find `\Shp`:: Shift + Help `\Smg`:: Shift + Message `\Smv`:: Shift + Move `\Snx`:: Shift + Next `\Sop`:: Shift + Options `\Spr`:: Shift + Print `\Spv`:: Shift + Previous `\Srd`:: Shift + Redo `\Sre`:: Shift + Resume `\Srp`:: Shift + Replace `\Ssu`:: Shift + Suspend `\Ssv`:: Shift + Save `\Sud`:: Shift + Undo INTR, EOF, KILL, ERASE の四つは stty コマンドなどで設定される端末の特殊文字です。一般的な環境では、INTR は Ctrl + C に、EOF は Ctrl + D に、KILL は Ctrl + U に、ERASE は Ctrl + H または Ctrl + ? に設定されています。これら四つは他のキー入力よりも優先して認識されます。 [[completion]] == コマンドライン補完 行編集でコマンドを入力している途中で Tab キーを押すことで、コマンドの名前やオプション、引数を補完することができます。コマンド名やファイル名を途中まで打ち込んだところで Tab キーを押すと、その名前に一致するコマンド名やファイル名の一覧が現れます。さらに続けて Tab キーを押すと、入力したい名前を一覧の中から選ぶことができます。(一致する名前が一つしかない場合は、一覧は現れず、直接名前がコマンドラインに入力されます。) 補完の対象となる名前に +*+ や +?+ などの文字が入っている場合は、その{zwsp}link:pattern.html[パターン]に一致する名前全てがコマンドラインに展開されます。(一覧による選択はできません) 標準状態では、コマンド名を入力しているときはコマンド名が、コマンドの引数を入力しているときはファイル名が補完されます。しかし補完を行う関数 (dfn:[補完関数]) を定義することで補完内容を変更することができます。 [[completion-detail]] === 補完動作の詳細 シェルを起動してから初めて補完を行おうとしたとき、コマンド +link:_dot.html[.] -AL completion/INIT+ を実行するのと同様にして、ファイル completion/INIT を{zwsp}link:params.html#sv-yash_loadpath[ロードパス]から読み込みます。ファイルが見つからない場合は、そのまま補完動作を続けます。(この処理は主にシェルに付属している補完関数を読み込むためのものですが、ロードパス内に自分で用意したファイルを置いておくことで代わりにそれを読み込ませることもできます。) 補完は、対象に応じて以下のように行います。 コマンド名:: 関数 +completion//command+ が定義されている場合は、それを補完関数として実行します。定義されていない場合は、入力中の単語をコマンド名として補完します。 コマンドの引数:: 関数 +completion//argument+ が定義されている場合は、それを補完関数として実行します。定義されていない場合は、入力中の単語をファイル名として補完します。 この他、{zwsp}link:expand.html#tilde[チルダ展開]におけるユーザ名や{zwsp}link:expand.html#params[パラメータ展開]におけるパラメータ名を入力しているときは、ユーザ名やパラメータ名が常に補完されます。(補完のしかたを変更することはできません) 補完関数は普通の{zwsp}link:exec.html#function[関数]と同様に (link:params.html#positional[位置パラメータ]なしで) 実行されます。ただし、補完関数の実行時には以下の{zwsp}link:exec.html#localvar[ローカル変数]が自動的に設定されます。 link:params.html#sv-ifs[+IFS+]:: 空白文字・タブ・改行の三文字 (シェル起動時のデフォルト) +WORDS+:: 既に入力されているコマンド名とコマンドの引数を含む{zwsp}link:params.html#arrays[配列]です。コマンド名を補完しようとしているときは、この配列は空になります。 +TARGETWORD+:: 現在補完を行おうとしている、途中まで入力されたコマンド名またはコマンドの引数です。 補完関数の中で link:_complete.html[complete 組込みコマンド]を実行することで、補完候補が生成されます。シェルは補完関数実行中に生成された補完候補を用いて補完を行います。 補完関数の実行中は、端末に対して入出力を行ってはなりません (端末の表示が乱れてしまいます)。スムーズな補完を行うために、補完の実行中に長い時間のかかる処理を行うのは避けてください。 補完の実行中は、{zwsp}link:posix.html[POSIX 準拠モード]と link:_set.html#so-errreturn[err-return オプション]が強制的に解除されます。また link:_set.html#so-errexit[err-exit オプション]は無視され、{zwsp}link:_trap.html[トラップ]は実行されません。 [[prediction]] == コマンドライン推定 コマンドライン推定は実験的な機能です。{zwsp}link:_set.html#so-lepredict[Le-predict] オプションが有効なとき、行編集で入力したコマンドの部分に続けて入力されそうなコマンド文字列を推定します。 例えば、あなたは既に一度 +ls Documents+ というコマンドを実行したとします。次にあなたが +ls Doc+ と打ったとき、シェルはカーソルの直後に +uments+ と表示します。入力しようとしているコマンドがこの通りなら、コマンドを打ち続ける代わりにカーソルを右に動かす (forward-char コマンドを始めどの<>でも構いません) ことでコマンドを入力できます。最後の +s+ の後ろまでカーソルを動かした後、accept-line コマンドでコマンドを実行できます。あるいは、カーソルを動かさなくても accept-prediction コマンドを使えば推定されたコマンドを直接実行できます。 打ったコマンドと推定されたコマンドの部分を区別するために、{zwsp}link:params.html#sv-ps1s[+PS1S+] 変数を設定して打ったコマンドの部分の{zwsp}link:interact.html#prompt[フォントの表示を変更]することができます。推定された部分の表示を変更する機能は (まだ) 提供されていません。 カーソルを右に動かしたとき、カーソルを動かした位置までの部分は実際に打ったのと同様に取り扱われます。しかしその後カーソルを左に戻しても入力を取り消したことにはなりません。推定機能を使っているかどうかに関係なく、backward-delete-char などのコマンドで文字を削除する必要があります。 コマンドの推定はカーソルが入力中のコマンドの末尾にあるときのみ機能します。 デフォルトでは、コマンドを打ち始めると推定が行われます。{zwsp}link:_set.html#so-lepredictempty[Le-predict-empty] オプションを有効にするとコマンドを一文字も入力する前から推定されたコマンドが表示されます。 推定アルゴリズムは{zwsp}link:interact.html#history[コマンド履歴]に基づいてコマンドを推定します。より新しい履歴はより確率が高いと判断します。また複数行に亘るコマンドの出現パターンも考慮します。確率が十分に高いと判断した部分のみを推定結果として表示するため、推定結果は必ずしも完全なコマンドにはなりません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/params.txt000066400000000000000000000544771354143602500156230ustar00rootroot00000000000000= パラメータと変数 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - パラメータと変数 :description: Yash が扱うパラメータの種類や特別な意味を持つ変数について dfn:[パラメータ]とは、{zwsp}link:expand.html[パラメータ展開]で値に置き換えられるデータを言います。パラメータには<>・<>・<>の三種類があります。 [[positional]] == 位置パラメータ dfn:[位置パラメータ]は 1 以上の自然数によって識別されるパラメータです。例えば位置パラメータが三つある場合、それらは順に +1+, +2+, +3+ という名称で識別されます。位置パラメータの個数は<> で取得できます。また全ての位置パラメータを表す特殊パラメータとして +*+ と +@+ があります。 位置パラメータは、シェルの起動時に、シェルのコマンドライン引数を元に初期化されます (link:invoke.html#arguments[起動時のコマンドライン引数]参照)。引数のうち、位置パラメータの値として与えられたオペランドが順に一つずつ位置パラメータとなります。 シェルのコマンド実行中に{zwsp}link:exec.html#function[関数]が呼び出されるとき、位置パラメータはその関数の呼び出しに対する引数に変更されます。すなわち、関数の実行中は位置パラメータによって関数の引数を参照できます。関数呼び出しの直前の位置パラメータの値は保存されており、関数の実行が終了する際に元の値に戻ります。 位置パラメータは、{zwsp}link:_set.html[set] や link:_shift.html[shift] などの組込みコマンドによって変更できます。 +0+ は位置パラメータとは見なされません (特殊パラメータの一つです)。 [[special]] == 特殊パラメータ dfn:[特殊パラメータ]は一文字の記号によって識別されるパラメータです。特殊パラメータにはユーザが明示的に値を代入することはできません。 Yash では以下の特殊パラメータが利用可能です。 [[sp-zero]]+0+:: このパラメータの値は、シェルの起動時に与えられたシェルの実行ファイルの名称またはスクリプトファイルの名称です。(link:invoke.html#arguments[起動時のコマンドライン引数]参照) [[sp-hash]]+#+:: このパラメータの値は、現在の位置パラメータの個数を表す 0 以上の整数です。 [[sp-dollar]]+$+:: このパラメータの値は、シェル自身のプロセス ID を表す正の整数です。この値はサブシェルにおいても変わりません。 [[sp-hyphen]]+-+:: このパラメータの値は、現在シェルで有功になっているオプションの文字をつなげたものです。この値には、{zwsp}link:invoke.html[シェルの起動]時にコマンドライン引数で指定できる一文字のオプションのうち現在有効になっているものが全て含まれます。{zwsp}link:_set.html[Set 組込みコマンド]でオプションを変更した場合は、その変更がこのパラメータの値にも反映されます。 [[sp-question]]+?+:: このパラメータの値は、最後に終了した{zwsp}link:syntax.html#pipelines[パイプライン]の終了ステータスを表す 0 以上の整数です。 [[sp-exclamation]]+!+:: このパラメータの値は、最後に実行した{zwsp}link:syntax.html#async[非同期コマンド]のプロセス ID です。 [[sp-asterisk]]+*+:: このパラメータの値は、現在の<>の値です。位置パラメータが一つもない場合、このパラメータの値は空文字列です。位置パラメータが複数ある場合、このパラメータの値は全ての位置パラメータの値を連結したものです。各位置パラメータの値の間は以下に従って区切られます。 + -- - 変数 <> が存在し、その値が空でない場合、各位置パラメータは変数 +IFS+ の値の最初の文字で区切られます。 - 変数 +IFS+ が存在し、その値が空の場合、各位置パラメータは間に何も置かずに連結されます。 - 変数 +IFS+ が存在しない場合、各位置パラメータは空白文字で区切られます。 -- + このパラメータの展開結果に対して{zwsp}link:expand.html#split[単語分割]が行われる場合、値はまず元の位置パラメータに一致するように分割されさらに変数 +IFS+ の値に従って分割されます。この最初の分割は +IFS+ が空文字列でも行います。 [[sp-at]]+@+:: このパラメータは、パラメータ +*+ と同様に現在の全ての<>を表します。ただし、このパラメータが二重引用符による{zwsp}link:syntax.html#quotes[クォート]の中で{zwsp}link:expand.html#params[展開]される場合の扱いがパラメータ +*+ と異なります。この場合、結果は各位置パラメータに正確に一致するように{zwsp}link:expand.html#split[単語分割]されます。また位置パラメータが一つもない場合、このパラメータは展開後の単語には残りません。(よって、引用符の中であるにもかかわらず、展開結果は一つの単語になるとは限りません。) + 例えば位置パラメータが一つもないとき、コマンドライン +echo 1 "$@" 2+ は ++echo++、++1++、++2++ という三つの単語に展開されます。位置パラメータが ++1++、++2 2++、++3++ の三つのとき、コマンドライン +echo "$@"+ は ++echo++、++1++、++2 2++、++3++ という四つの単語に展開され、コマンドライン +echo "a$@b"+ は ++echo++、++a1++、++2 2++、++3b++ という四つの単語に展開されます。 [[variables]] == 変数 dfn:[変数]とはユーザが自由に代入可能なパラメータです。各変数は名前で区別され、それぞれが文字列の値を持ちます。 変数の名前は、英数字と下線 (+_+) から構成されます。ただし変数名の頭文字を数字にすることはできません。環境によってはこれ以外の文字も変数名に使用できます。 シェルが扱う変数のうち、エクスポートの対象となっているものはdfn:[環境変数]といいます。これらの変数はシェルが外部コマンドを起動する際に外部コマンドに渡されます。シェルが起動されたときにシェルを起動したプログラムから渡された変数は自動的に環境変数になります。 変数は、{zwsp}link:syntax.html#simple[単純コマンド]によって代入できます。また link:_typeset.html[typeset 組込みコマンド]などでも変数に代入することができます。変数を削除するには link:_unset.html[unset 組込みコマンド]を使います。 [[shellvars]] === シェルが使用する変数 以下の名前の変数は、yash の実行において特別な意味を持っています。 [[sv-cdpath]]+CDPATH+:: この変数は link:_cd.html[cd 組込みコマンド]で移動先ディレクトリを検索するために使われます。 [[sv-columns]]+COLUMNS+:: この変数は端末ウィンドウの横幅 (文字数) を指定します。この変数が設定されている場合、デフォルトの横幅ではなくこの変数の値で指定された横幅が{zwsp}link:lineedit.html[行編集]で使われます。 [[sv-command_not_found_handler]]+COMMAND_NOT_FOUND_HANDLER+:: シェルが実行しようとしたコマンドが見つからなかったとき、この変数の値がコマンドとして実行されます。不明なコマンドを実行したときに何か別のコマンドを実行させたい時に便利です。{zwsp}link:exec.html#simple[単純コマンドの実行]を参照。 + この機能は link:posix.html[POSIX 準拠モード]では働きません。 [[sv-dirstack]]+DIRSTACK+:: この配列変数はディレクトリスタックの実装に使われています。{zwsp}link:_pushd.html[pushd 組込みコマンド]でディレクトリを移動したとき、前のディレクトリを覚えておくためにそのパス名がこの配列に入れられます。この配列の内容を変更することは、ディレクトリスタックの内容を直接変更することになります。 [[sv-echo_style]]+ECHO_STYLE+:: この変数は link:_echo.html[echo 組込みコマンド]の挙動を指定します。 [[sv-env]]+ENV+:: link:posix.html[POSIX 準拠モード]で対話モードのシェルが起動されたとき、この変数の値で示されるパスのファイルが初期化スクリプトとして読み込まれます (link:invoke.html#init[シェルの初期化処理]参照)。 [[sv-fcedit]]+FCEDIT+:: link:_fc.html[Fc 組込みコマンド]でコマンドを編集する際、この変数の値で示されたエディタがコマンドの編集に使われます。 [[sv-handled]]+HANDLED+:: この変数は +COMMAND_NOT_FOUND_HANDLER+ 変数の値が実行された後に、コマンドが見つからなかったことをエラーとするかどうかを指示します。{zwsp}link:exec.html#simple[単純コマンドの実行]を参照。 [[sv-histfile]]+HISTFILE+:: link:interact.html#history[コマンド履歴]を保存するファイルのパスを指定します。 [[sv-histrmdup]]+HISTRMDUP+:: link:interact.html#history[コマンド履歴]の重複をチェックする個数を指定します。履歴にコマンドを追加する際、既に履歴にあるコマンドのうちここで指定した個数のコマンドが新しく追加されるコマンドと同じかどうかをチェックします。同じコマンドが既に履歴にあれば、それは履歴から削除されます。 + 例えばこの変数の値が +1+ のときは、履歴に追加されるコマンドが一つ前のコマンドと同じならばそれは削除されます。それより古い履歴のコマンドは、(履歴に追加されるコマンドと同じでも) 削除されません。もしこの変数の値が +HISTSIZE+ 変数の値と同じなら、履歴の中で重複するコマンドはすべて削除されます。あるいはもしこの変数の値が +0+ なら、重複する履歴は一切削除されません。 [[sv-histsize]]+HISTSIZE+:: link:interact.html#history[コマンド履歴]に保存される履歴項目の個数を指定します。 [[sv-home]]+HOME+:: ユーザのホームディレクトリのパスを指定します。{zwsp}link:expand.html#tilde[チルダ展開]や link:_cd.html[cd 組込みコマンド]の動作に影響します。 [[sv-ifs]]+IFS+:: この変数は{zwsp}link:expand.html#split[単語分割]の区切りを指定します。シェルの起動時にこの変数の値は空白文字・タブ・改行の三文字に初期化されます。 [[sv-lang]]+LANG+:: [[sv-lc_all]]+LC_ALL+:: [[sv-lc_collate]]+LC_COLLATE+:: [[sv-lc_ctype]]+LC_CTYPE+:: [[sv-lc_messages]]+LC_MESSAGES+:: [[sv-lc_monetary]]+LC_MONETARY+:: [[sv-lc_numeric]]+LC_NUMERIC+:: [[sv-lc_time]]+LC_TIME+:: これらの変数はシェルが動作するロケールを指定します。シェルが読み書きするファイルのエンコーディングやエラーメッセージの内容などはこの変数で指定されたロケールに従います。 + +LC_CTYPE+ 変数の値はシェルの起動時にのみ反映されます。シェルの実行中にこの変数を変更してもシェルのロケールは変わりません (シェルが非 link:posix.html[POSIX 準拠モード]で{zwsp}link:interact.html[対話モード]の場合を除く)。 [[sv-lineno]]+LINENO+:: この変数の値は、現在シェルが読み込んで実行しているファイルにおける、現在実行中のコマンドのある行番号を示します。(link:interact.html[対話モード]では、コマンドを入力して実行するたびに行番号は 1 に戻ります) + 一度この変数に代入したり変数を削除したりすると、この変数を用いて行番号を取得することはできなくなります。 [[sv-lines]]+LINES+:: この変数は端末ウィンドウの行数を指定します。この変数が設定されている場合、デフォルトの行数ではなくこの変数の値で指定された行数が{zwsp}link:lineedit.html[行編集]で使われます。 [[sv-mail]]+MAIL+:: この変数は{zwsp}link:interact.html#mailcheck[メールチェック]の対象となるファイルのパスを指定します。 [[sv-mailcheck]]+MAILCHECK+:: この変数は{zwsp}link:interact.html#mailcheck[メールチェック]を行う間隔を秒単位で指定します。この変数の値はシェルの起動時に +600+ に初期化されます。 [[sv-mailpath]]+MAILPATH+:: この変数は{zwsp}link:interact.html#mailcheck[メールチェック]の対象となるファイルのパスを指定します。 [[sv-nlspath]]+NLSPATH+:: POSIX によるとこの変数の値はロケール依存のメッセージデータファイルのパスを指示することになっていますが、yash では使用していません。 [[sv-oldpwd]]+OLDPWD+:: link:_cd.html[Cd 組込みコマンド]などで作業ディレクトリを変更したときに、変更前の作業ディレクトリパスがこの変数に設定されます。この変数はデフォルトでエクスポート対象になります。 [[sv-optarg]]+OPTARG+:: link:_getopts.html[Getopts 組込みコマンド]で引数付きのオプションを読み込んだとき、その引数の値がこの変数に設定されます。 [[sv-optind]]+OPTIND+:: この変数の値は、{zwsp}link:_getopts.html[getopts 組込みコマンド]で次に読み込むオプションのインデックスを表します。シェルの起動時にこの変数は +1+ に初期化されます。 [[sv-path]]+PATH+:: この変数は、{zwsp}link:exec.html#search[コマンドの検索時]にコマンドのありかを示すパスを指定します。 [[sv-ppid]]+PPID+:: この変数の値は、シェルの親プロセスのプロセス ID を表す正の整数です。この変数はシェルの起動時に初期化されます。この変数の値は{zwsp}link:exec.html#subshell[サブシェル]においても変わりません。 [[sv-prompt_command]]+PROMPT_COMMAND+:: link:posix.html[POSIX 準拠モード]でない{zwsp}link:interact.html[対話モード]のシェルにおいて、シェルが各コマンドのプロンプトを出す直前に、この変数の値がコマンドとして解釈・実行されます。これは、プロンプトを出す直前に毎回 ifdef::basebackend-html[] pass:[eval -i -- "${PROMPT_COMMAND-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${PROMPT_COMMAND-}"`] というコマンドが実行されるのと同じですが、このコマンドの実行結果は次のコマンドでの +?+ 特殊パラメータの値には影響しません。 [[sv-ps1]]+PS1+:: この変数の値は、対話モードのシェルが出力する標準のコマンドプロンプトを指定します。この値の書式については{zwsp}link:interact.html#prompt[プロンプト]の項を参照してください。 + この変数はシェルの起動時に実効ユーザ ID が 0 かどうかによって +$ + と +# + のどちらかに初期化されます。 [[sv-ps1r]]+PS1R+:: この変数の値は、対話モードのシェルがコマンドを読み込む際に、入力されるコマンドの右側に表示されるプロンプトを指定します。この値の書式については{zwsp}link:interact.html#prompt[プロンプト]の項を参照してください。 [[sv-ps1s]]+PS1S+:: この変数の値は、対話モードのシェルがコマンドを読み込む際に、入力されるコマンドを表示するフォントの書式を指定します。この値の書式については{zwsp}link:interact.html#prompt[プロンプト]の項を参照してください。 [[sv-ps2]]+PS2+:: この変数の値は、対話モードのシェルが出力する補助的なコマンドプロンプトを指定します。この値の書式については{zwsp}link:interact.html#prompt[プロンプト]の項を参照してください。 この変数はシェルの起動時に +> + に初期化されます。 [[sv-ps2r]]+PS2R+:: この変数は <> 変数と同様ですが、プロンプトとして <> 変数ではなく <> 変数の値が使用されるときに使用されます。 [[sv-ps2s]]+PS2S+:: この変数は <> 変数と同様ですが、プロンプトとして <> 変数ではなく <> 変数の値が使用されるときに使用されます。 [[sv-ps4]]+PS4+:: link:_set.html#options[Xtrace オプション]が有効なとき、この変数の値が各トレース出力の前に出力されます。ただし出力の前にこの変数の値に対して{zwsp}link:expand.html#params[パラメータ展開]、{zwsp}link:expand.html#cmdsub[コマンド置換]、{zwsp}link:expand.html#arith[数式展開]を行います。また link:posix.html[POSIX 準拠モード]でなければ、<> 変数と同様に、バックスラッシュで始まる特殊な記法が利用できます。 + この変数はシェルの起動時に ++ + に初期化されます。 [[sv-ps4s]]+PS4S+:: この変数は <> 変数と同様ですが、プロンプトとして <> 変数が使用されるときではなく、トレース出力の際に <> 変数の値が使用されるときに使用されます。この変数を使うとトレース出力のフォントの書式を変更することができます。 [[sv-pwd]]+PWD+:: この変数の値は現在の作業ディレクトリの絶対パスを表します。この変数はシェルの起動時に正しいパスに初期化され、{zwsp}link:_cd.html[cd 組込みコマンド]などで作業ディレクトリを変更する度に再設定されます。この変数はデフォルトでエクスポート対象になります。 [[sv-random]]+RANDOM+:: この変数は乱数を取得するために使用できます。この変数の値は 0 以上 32768 未満の一様分布乱数になっています。 + この変数に非負整数を代入すると乱数を生成する__種__を再設定できます。 + 一度この変数を削除すると、この変数を用いて乱数を取得することはできなくなります。またシェルが link:posix.html[POSIX 準拠モード]で起動された場合、この変数で乱数を取得することはできません。 [[sv-term]]+TERM+:: この変数は対話モードのシェルが動作している端末の種類を指定します。ここで指定された端末の種類に従って{zwsp}link:lineedit.html[行編集]機能は端末を制御します。この変数の効力を得るためには変数がエクスポートされている必要があります。 [[sv-yash_after_cd]]+YASH_AFTER_CD+:: この変数の値は、{zwsp}link:_cd.html[cd 組込みコマンド]や link:_pushd.html[pushd 組込みコマンド]で作業ディレクトリが変更された後にコマンドとして解釈・実行されます。これは、作業ディレクトリが変わった後に毎回 ifdef::basebackend-html[] pass:[eval -i -- "${YASH_AFTER_CD-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${YASH_AFTER_CD-}"`] というコマンドが実行されるのと同じです。 [[sv-yash_loadpath]]+YASH_LOADPATH+:: link:_dot.html[ドット組込みコマンド]で読み込むスクリプトファイルのあるディレクトリを指定します。<> 変数と同様に、コロンで区切って複数のディレクトリを指定できます。この変数はシェルの起動時に、yash に付属している共通スクリプトのあるディレクトリ名に初期化されます。 [[sv-yash_le_timeout]]+YASH_LE_TIMEOUT+:: この変数は{zwsp}link:lineedit.html[行編集]機能で曖昧な文字シーケンスが入力されたときに、入力文字を確定させるためにシェルが待つ時間をミリ秒単位で指定します。行編集を行う際にこの変数が存在しなければ、デフォルトとして 100 ミリ秒が指定されます。 [[sv-yash_ps1]]+YASH_PS1+:: [[sv-yash_ps1r]]+YASH_PS1R+:: [[sv-yash_ps1s]]+YASH_PS1S+:: [[sv-yash_ps2]]+YASH_PS2+:: [[sv-yash_ps2r]]+YASH_PS2R+:: [[sv-yash_ps2s]]+YASH_PS2S+:: [[sv-yash_ps4]]+YASH_PS4+:: [[sv-yash_ps4s]]+YASH_PS4S+:: link:posix.html[POSIX 準拠モード]ではないとき、これらの変数は名前に +YASH_+ が付かない +PS1+ 等の変数の代わりに優先して使われます。POSIX 準拠モードではこれらの変数は無視されます。{zwsp}link:interact.html#prompt[プロンプト]で yash 固有の記法を使用する場合はこれらの変数を使用すると POSIX 準拠モードで yash 固有の記法が解釈されずに表示が乱れるのを避けることができます。 [[sv-yash_version]]+YASH_VERSION+:: この変数はシェルの起動時にシェルのバージョン番号に初期化されます。 [[arrays]] === 配列 dfn:[配列]とは、一つの変数に複数の値 (文字列) を持たせたものです。一つの配列の複数の値は<>と同様に 1 以上の自然数で識別されます。 配列は、{zwsp}link:syntax.html#simple[単純コマンド]によって代入できます。また link:_array.html[array 組込みコマンド]などでも配列に代入することができます。配列を削除するには変数と同様に link:_unset.html[unset 組込みコマンド]を使います。 配列を配列のままエクスポートすることはできません。配列をエクスポートしようとすると、配列の各値をコロンで区切って繋いだ一つの文字列の値を持つ変数としてエクスポートされます。 link:posix.html[POSIX 準拠モード]では配列は使えません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/pattern.txt000066400000000000000000000205311354143602500157750ustar00rootroot00000000000000= パターンマッチング記法 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - パターンマッチング記法 :description: Yash がサポートするパターンマッチング記法について dfn:[パターンマッチング記法]は特定の文字列の集合を表すdfn:[パターン]の書式と意味の定義です。ある文字列があるパターンの表す文字列の集合に含まれる時、その文字列はそのパターンにdfn:[マッチする]といいます。文字列がパターンに当てはまるかどうかは、以下に示す定義に従って判定されます。 [[normal]] == 通常の文字 link:syntax.html#quotes[クォート]してある文字および以下に示す特殊な意味を持つ文字以外のすべての文字は、通常の文字として扱われます。パターンに含まれる通常の文字は、その文字自身に当てはまります。 例えば +abc+ というパターンは +abc+ という文字列に当てはまります。(そしてこれ以外の文字列には一切当てはまりません) [[single]] == 一文字ワイルドカード 文字 +?+ は任意の一文字に当てはまります。 例えば +a?c+ というパターンは ++aac++、++abc++、++a;c++ など、+a+ で始まり +c+ で終わる任意の 3 文字の文字列に当てはまります。 [[multiple]] == 複数文字ワイルドカード 文字 +*+ は任意の文字列に当てはまります。ここでいう任意の文字列には空文字列も含まれます。 例えば +a*c+ というパターンは ++ac++、++abc++、++a;xyz;c++ など、+a+ で始まり +c+ で終わる任意の文字列に当てはまります。 [[bracket]] == ブラケット記法 括弧 +[+ と +]+ で囲まれた部分はdfn:[ブラケット記法]とみなされます。ただし、括弧の間には少なくとも一文字挟まれている必要があります。括弧の間にある文字は以下に示すブラケット記法のための特殊なパターン (dfn:[ブラケット記法パターン]) として解釈されます。ブラケット記法は、括弧の間にあるブラケット記法パターンが示す文字のどれか一つに当てはまります。 最初の括弧 +[+ の直後に記号 +!+ がある場合、ブラケット記法に当てはまる文字と当てはまらない文字とが逆転します (そしてこの +!+ はブラケット記法パターンの一部とはみなされません)。Yash では +[+ の直後に +^+ がある場合も同様に当てはまる文字と当てはまらない文字とが逆転します (が、他のシェルでは +^+ の扱いが異なることもあります)。 最初の括弧 +[+ の直後 (あるいは上述の +!+ または +^+ がある場合はその直後) に括弧 +]+ がある場合は、それはブラケット記法の終わりを示す括弧としてではなくブラケット記法パターンの一部とみなされます。ブラケット記法パターンの解釈は{zwsp}link:syntax.html#quotes[クォート]の処理の後に行われるので、クォートによってブラケット記法パターン内の文字を通常の文字として扱わせることはできません。 パターンの中に +[+ が含まれていても、それが正しいブラケット記法の形式になっていない場合は、その +[+ はブラケット記法ではなく通常の文字として扱われます。 [[bra-normal]] == (ブラケット記法パターンにおける) 通常の文字 以下に示す特殊な意味を持つ記号以外の文字はすべて通常の文字として扱われます。通常の文字はその文字自身を表します。 例えば +[abc]+ というブラケット記法は ++a++、++b++、++c++ のどれかの文字に当てはまります。従って +a[abc]c+ というパターンは ++aac++、++abc++、++acc++ という三つの文字列に当てはまります (そしてこれ以外の文字列には当てはまりません)。 [[bra-range]] == 範囲指定 二つの文字 (または<>) をハイフン (+-+) でつないだものはdfn:[範囲指定]とみなされます。範囲指定は、その二つの文字と照合順序上その間にある全ての文字を表します。dfn:[照合順序]とは文字を辞書順に並べるためにロケールデータに定義される文字の順序関係です。使用中のロケールに定義されている照合順序に従って二つの文字の間にある文字が決まります。 ハイフンの後に +]+ を置いた場合は、この +]+ はブラケット記法の終わりを示す括弧とみなされ、ハイフンは通常の文字として扱われます。 例えば +[1-5]+ というブラケット記法は ++1++、++2++、++3++、++4++、++5++ という五つの文字のどれか一つに当てはまります。 [[bra-colsym]] == 照合シンボル dfn:[照合シンボル]を用いることで複数の文字からなる照合要素を一つの文字として扱うことができます。(dfn:[照合要素]とは複数の文字をまとめて一つの文字として扱うことができるようにするために考えられた、より一般的な文字の概念です。パターンマッチングにおいて全ての文字は実際には照合要素として扱われています。) 照合シンボルは括弧 +[. .]+ の中に照合要素を挟んだものとして表します。括弧内に書ける照合要素は使用中のロケールデータにおいて照合要素として登録されているものに限ります。 例えば従来スペイン語では ``ch'' という二文字を合わせて一文字として扱っていました。この二文字の組み合わせが照合要素としてロケールに登録されているならば、++[[.ch.]df]++ というブラケット記法は ++ch++、++d++、++f++ のどれかに当てはまります。もしここで +[chdf]+ というブラケット記法を使うと、これは ++c++、++h++、++d++、++f++ のどれかに当てはまり、++ch++ には当てはまりません。 [[bra-eqclass]] == 等価クラス 等価クラスを用いることで、ある文字と__等価__であるとみなされる文字を指定することができます。等価クラスは括弧 +[= =]+ の中に文字を挟んだものとして表します。括弧の間には照合シンボルのように複数の文字からなる照合要素を書くこともできます (上記参照)。等価クラスは、括弧で挟んだ文字そのものの他に、その文字と同じ第一等価クラスに属する全ての文字を表します。どの文字が第一等価クラスに属するかの定義は使用中のロケールデータに従います。 例えばロケールデータにおいて a, à, á, â, ã, ä の 6 文字が同じ第一等価クラスに属すると定義されているとき、+[[=a=]]+ というブラケット記法はこれら六つの文字のどれか一つに当てはまります。+[[=à=]]+ や +[[=á=]]+ も同様です。 [[bra-chclass]] == 文字クラス dfn:[文字クラス]は特定の種類の文字の集合を表します。文字クラスは括弧 +[: :]+ の間に文字クラスの名前を囲んだものとして表します。文字クラスの名前としては、以下に挙げる共通の文字クラスの他に、使用中のロケールで定義された独自の文字クラスが使用できます。いずれの文字クラスの場合も、文字クラスにどの文字が含まれるのかは使用中のロケールにおける文字クラスの定義に従います。 +[:lower:]+:: 小文字の集合 +[:upper:]+:: 大文字の集合 +[:alpha:]+:: アルファベットの集合 (+[:lower:]+ と +[:upper:]+ を含む) +[:digit:]+:: 十進法の数字の集合 +[:xdigit:]+:: 十六進法の数字の集合 +[:alnum:]+:: アルファベットと数字の集合 (+[:alpha:]+ と +[:digit:]+ を含む) +[:blank:]+:: 空白文字の集合 (改行を含まない) +[:space:]+:: 空白文字の集合 (改行等を含む) +[:punct:]+:: 句読点等の集合 +[:print:]+:: 表示可能な文字の集合 +[:cntrl:]+:: 制御文字の集合 例えば `[[:lower:][:upper:]]` というブラケット記法は一文字の小文字または大文字に当てはまります。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/posix.txt000066400000000000000000000152361354143602500154700ustar00rootroot00000000000000= POSIX 準拠モード :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - POSIX 準拠モード :description: Yash の POSIX 準拠モードの動作について Yash は基本的に POSIX.1-2008 のシェルの規定に従って動作しますが、利便性や分かりやすさのために POSIX の規定とは異なる動作をする点もあります。そのため標準状態の yash は POSIX の規定するシェルとして供するには向きません。dfn:[POSIX 準拠モード]を有効にすると、yash はできる限り POSIX の規定通りに動作するようになります。POSIX 準拠シェルとしての互換性が必要な場面では、POSIX 準拠モードを有効にしてください。 link:invoke.html[シェルの起動]時の起動時の名前が +sh+ ならばシェルは自動的に POSIX 準拠モードになります。また起動時に +-o posixlycorrect+ オプションが指定されている場合も POSIX 準拠モードになります。また起動後は、+link:_set.html[set] -o posixlycorrect+ を実行することで POSIX 準拠モードを有効にできます。 POSIX 準拠モードを有効にすると、yash は POSIX の規定にできるだけ従うようになるだけでなく、POSIX が__未定義__や__未規定__と定めている場合のほとんどをエラーにするようになります。すなわち、yash 独自の拡張機能の多くは使えなくなります。具体的には、POSIX 準拠モードを有効にすると以下のような挙動の変化があります。 - シェルの起動時の{zwsp}link:invoke.html#init[初期化]で読み込むスクリプトファイルが異なります。 - シェルが link:invoke.html#arguments[+-c+ オプション]で起動された場合、構文エラー時に +yash -c+ の代わりに +sh -c+ をファイル名として表示します。 - グローバル{zwsp}link:syntax.html#aliases[エイリアス]の置換を行いません。 - link:syntax.html#compound[複合コマンド]の{zwsp}link:syntax.html#grouping[グルーピング]や link:syntax.html#if[if 文]の内容が空の場合エラーになります。 - link:syntax.html#for[For ループ]で展開した単語は link:_set.html#so-forlocal[for-local オプション]に関係なくグローバル変数として代入します。変数名はポータブルな (すなわち ASCII の範囲内の) 文字しか使えません。 - link:syntax.html#case[Case 文]の最初のパターンを +esac+ にすることはできません。 - 予約語 +!+ の直後に空白を置かずに +(+ を置くことはできません。 - link:syntax.html#double-bracket[二重ブラケットコマンド]は使えません。 - 予約語 +function+ を用いる形式の{zwsp}link:syntax.html#funcdef[関数定義]構文は使えません。関数名はポータブルな (すなわち ASCII の範囲内の) 文字しか使えません。 - link:syntax.html#simple[単純コマンド]での{zwsp}link:params.html#arrays[配列]の代入はできません。 - シェル実行中に link:params.html#sv-lc_ctype[+LC_CTYPE+ 変数]の値が変わっても、それをシェルのロケール情報に反映しません。 - link:params.html#sv-random[+RANDOM+ 変数]は使えません。 - link:expand.html#tilde[チルダ展開]で +~+ と +~{{ユーザ名}}+ 以外の形式の展開が使えません。 - link:expand.html#params[パラメータ展開]の{zwsp}link:expand.html#param-name[入れ子]はできません。また{zwsp}link:expand.html#param-index[インデックス]および{{単語2}}のある{zwsp}link:expand.html#param-mod[加工指定]は使用できません。 - +$(+ と +)+ で囲んだ{zwsp}link:expand.html#cmdsub[コマンド置換]に含まれるコマンドは、コマンド置換が実行される時に毎回解析されます。 - link:expand.html#arith[数式展開]で小数ならびに `++` および `--` 演算子が使えません。数値でない変数は常にエラーになります。 - link:redir.html[リダイレクト]の対象を示すトークンは次のリダイレクトのファイル記述子を示す整数と紛らわしくないようにしなければなりません。 - link:redir.html[リダイレクト]を伴う{zwsp}link:syntax.html#compound[複合コマンド]の直後に +}+ や +fi+ などの予約語を置くことはできません。 - link:redir.html#file[ファイルのリダイレクト]で、{zwsp}link:expand.html#glob[パス名展開]の結果が一つでない場合、すぐにはエラーにせず、パス名展開を行わなかったときと同様に扱います。 - +<&+ および +>&+ link:redir.html#dup[リダイレクト]演算子の対象となるファイル記述子はそれぞれ読み込み可能および書き込み可能でなければなりません。 - link:redir.html#socket[ソケットリダイレクト]・{zwsp}link:redir.html#here[ヒアストリング]・{zwsp}link:redir.html#pipe[パイプリダイレクト]・{zwsp}link:redir.html#process[プロセスリダイレクト]は使用できません。 - link:exec.html#simple[単純コマンドの実行]時、コマンドが見つからなくても link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+ 変数]の値は実行しません。 - link:exec.html#search[コマンドの検索]において{zwsp}link:builtin.html#types[通常の組込みコマンド]は対応する外部コマンドがないと見つかりません。 - いくつかの{zwsp}link:builtin.html[組込みコマンド]で特定のオプションが使えなくなるなど挙動が変わります。特に、長いオプションは使えなくなります。 - link:interact.html[対話モード]でないとき、{zwsp}link:builtin.html#types[特殊組込みコマンド]のオプションやオペランドの使い方が間違っているとシェルは直ちに終了します。また特殊組込みコマンドで代入エラーやリダイレクトエラーが発生したときも直ちに終了します。 - link:interact.html[対話モード]のプロンプトを出す前に link:params.html#sv-prompt_command[+PROMPT_COMMAND+ 変数]の値を実行しません。{zwsp}link:params.html#sv-ps1[+PS1+ 変数]・{zwsp}link:params.html#sv-ps2[+PS2+ 変数]・{zwsp}link:params.html#sv-ps4[+PS4+ 変数]の値の解釈の仕方が違います。{zwsp}link:params.html#sv-yash_ps1[+YASH_PS1+] など +YASH_+ で始まる名前のプロンプト変数は使用されません。 - link:interact.html#mailcheck[メールチェック]において、ファイルが更新されている場合はファイルが空でも新着メールメッセージを出力します。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/redir.txt000066400000000000000000000307421354143602500154320ustar00rootroot00000000000000= リダイレクト :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - リダイレクト :description: Yash がサポートするリダイレクト機能の説明 dfn:[リダイレクト]はコマンドのファイル記述子を変更する機能です。リダイレクトを使用すると、コマンドの標準入力や標準出力を通常とは異なるファイルに繋ぎ換えた状態でコマンドを実行することができます。 リダイレクトはコマンド (link:syntax.html#simple[単純コマンド]または{zwsp}link:syntax.html#compound[複合コマンド]) にリダイレクト演算子を付することで行います。単純コマンドでは (他のトークンとくっつかない限り) どこでもリダイレクト演算子を置くことができます。複合コマンドではコマンドの最後にリダイレクト演算子を付けます。 リダイレクトはコマンドの実行が始まる前に処理されます。一つのコマンドに複数のリダイレクトがある場合は、リダイレクト演算子が書いてある順序で処理されます。オペランドなしの link:_exec.html[exec 組込みコマンド]を実行する場合を除き、リダイレクトは対象となっているコマンドに対してのみ働きます。すなわち、対象のコマンドの実行が終わるとリダイレクトによって変更されたファイル記述子は元の状態に戻ります。 リダイレクト演算子は、+<+ または +>+ で始まります。+<+ で始まるリダイレクト演算子はデフォルトでは標準入力 (ファイル記述子 0) に作用します。+>+ で始まるリダイレクト演算子はデフォルトでは標準出力 (ファイル記述子 1) に作用します。どちらの種類の演算子でも、演算子の直前に非負整数を指定することでデフォルト以外のファイル記述子に作用させることができます (このとき整数と演算子との間に一切空白などを入れてはいけません。また整数を{zwsp}link:syntax.html#quotes[クォート]してもいけません)。 [[file]] == ファイルへのリダイレクト 最もよく使われるリダイレクトは、ファイルへのリダイレクトです。 入力のリダイレクト:: +< {{トークン}}+ 出力のリダイレクト:: +> {{トークン}}+ + +>| {{トークン}}+ + +>> {{トークン}}+ 入出力のリダイレクト:: +<> {{トークン}}+ リダイレクトに含まれる{{トークン}}は{zwsp}link:expand.html[四種展開]されます。{zwsp}link:interact.html[対話シェル]ではさらに{zwsp}link:expand.html#glob[パス名展開]も行われます (パス名展開の結果が一つのファイルでなければエラーです)。{{トークン}}の展開結果がリダイレクト対象のファイル名として使われます。 入力のリダイレクトでは標準入力が対象ファイルからの読み込み専用ファイル記述子に置き換えられます。対象ファイルを開くことができなければエラーになります。 出力のリダイレクトでは標準出力が対象ファイルへの書き込み専用ファイル記述子に置き換えられます。対象ファイルが存在しなければ空の通常ファイルが作成されます。対象ファイルが既にある場合はそのファイルが開かれます。ただし演算子の種類によって以下のように挙動が異なります。 - 演算子 +>|+ では、対象ファイルが存在しそれが通常のファイルの場合、ファイルを開く際にファイルの内容を空にします。 - 演算子 +>+ は、{zwsp}link:_set.html#so-clobber[clobber オプション]が有効ならば演算子 +>|+ と同じです。しかし clobber オプションが無効ならば、対象ファイルが存在しそれが通常のファイルの場合、エラーになります。 - 演算子 +>>+ では、ファイルを追記モードで開きます。ファイルへの書き込みは常にファイルの末尾へ追記する形で行われます。 入出力のリダイレクトでは標準入力が対象ファイルへの読み書き両用ファイル記述子に置き換えられます。対象ファイルが存在しなければ空の通常ファイルが作成されます。 [[socket]] === ソケットリダイレクト ファイルのリダイレクトにおいて、対象ファイル名が +/dev/tcp/{{ホスト名}}/{{ポート}}+ または +/dev/udp/{{ホスト名}}/{{ポート}}+ の形式をしていて、そのファイルを開くことができない場合、ファイル名に含まれる{{ホスト名}}と{{ポート}}に対して通信を行うためのソケットが開かれます。 +/dev/tcp/{{ホスト名}}/{{ポート}}+ が対象の場合はストリーム通信ソケットを、++/dev/udp/{{ホスト名}}/{{ポート}}++ が対象の場合はデータグラム通信ソケットを開きます。典型的には、前者は TCP を、後者は UDP をプロトコルとして使用します。 ソケットリダイレクトはどのリダイレクト演算子を使っているかにかかわらず常に読み書き両用のファイル記述子を開きます。 ソケットリダイレクトは POSIX 規格にはない yash の独自拡張です。ただし、bash にも同様の機能があります。 [[dup]] == ファイル記述子の複製 ファイル記述子の複製のリダイレクトでは、既存のファイル記述子をコピーしたり閉じたりできます。 ファイル記述子の複製:: +<& {{トークン}}+ + +>& {{トークン}}+ {{トークン}}は<>の場合と同様に展開されますが、これはファイル名ではなくファイル記述子として解釈されます。すなわち、{{トークン}}の展開結果はファイル記述子を表す非負整数となる必要があります。 演算子 +<&+ は{{トークン}}の展開結果で示されたファイル記述子を標準入力に複製します。演算子 +>&+ は{{トークン}}の展開結果で示されたファイル記述子を標準出力に複製します。演算子の直前に非負整数を指定することで、複製先のファイル記述子を変更できます。 {{トークン}}の展開結果が非負整数ではなくハイフン (+-+) となった場合は、ファイル記述子を複製する代わりに閉じます。演算子 +<&+ では標準入力が、演算子 +>&+ では標準出力がデフォルトで閉じられますが、演算子の直前に非負整数を指定することで、閉じるファイル記述子を変更できます。 link:posix.html[POSIX 準拠モード]では、++<&++ で複製するファイル記述子は読み込み可能でなければならず、++>&++ で複製するファイル記述子は書き込み可能でなければなりません。 [[here]] == ヒアドキュメントとヒアストリング dfn:[ヒアドキュメント]・dfn:[ヒアストリング]を使うとコマンドに直接テキストを渡すことができます。 ヒアドキュメント:: +<< {{トークン}}+ + +<<- {{トークン}}+ ヒアストリング:: +<<< {{トークン}}+ ヒアドキュメント・ヒアストリングでは、標準入力がヒアドキュメント・ヒアストリングの内容を読み込み可能なファイル記述子に置き換えられます。 ヒアドキュメント演算子 (+<<+ または +<<-+) がコマンド中に現れると、その演算子のある行の次の行からはヒアドキュメントの内容とみなされます。ヒアドキュメントの内容の部分は、シェルのコマンドとしては解釈されません。演算子の後にある{{トークン}}はヒアドキュメントの内容の終わりを表します。({{トークン}}では{zwsp}link:expand.html[展開]は行われませんが、{zwsp}link:syntax.html[クォート]は認識されます。) 演算子のある行より後の行で{{トークン}}だけからなる行が現れた時点でヒアドキュメントの内容は終わりだと判断されます。終わりを表す行はヒアドキュメントの内容には含まれません。演算子 +<<-+ を使っている場合は、ヒアドキュメントの内容の各行頭にあるタブはすべて削除されます。このとき{{トークン}}の前にタブがあっても (その行に他の余計な文字がなければ) ヒアドキュメントの内容の終わりとして認識します。 一行のコマンドに複数のリダイレクト演算子がある場合は、リダイレクトの内容は順番に処理されます。すなわち、その行の次の行からは最初のリダイレクトの内容として扱われ、その内容が終わったら、その次の行からは次のリダイレクトの内容として扱われます。最後のリダイレクトの内容が終わったら、その次の行からは再びコマンドとして解釈されます。 リダイレクトの内容は基本的に単なる文字列として扱われます。内容に含まれる空白やタブ、その他の記号はそのままコマンドに渡されます。ただし、{{トークン}}が全くクォートされていない場合は、ヒアドキュメントの内容は{zwsp}link:expand.html#params[パラメータ展開]・{zwsp}link:expand.html#cmdsub[コマンド置換]・{zwsp}link:expand.html#arith[数式展開]され、++$++, ++`++, ++"++, ++\++ の直前にある場合および行の連結を行う場合にのみバックスラッシュを{zwsp}link:syntax.html#quotes[引用符]として扱えます。 ヒアストリングでは、演算子の後にある{{トークン}}は<>の場合と同様に展開されます。この展開結果がヒアストリングの内容となります。ただしヒアストリングの内容の末尾には自動的に改行が付きます。 ヒアストリングは POSIX 規格にはない yash の独自拡張ですが、bash, ksh, zsh にも同様の機能があります。 [[pipe]] == パイプリダイレクト dfn:[パイプリダイレクト]を用いるとプロセス間通信に利用可能なパイプを開くことができます。 パイプリダイレクト:: +>>| {{トークン}}+ {{トークン}}は<>の場合と同様に展開されますが、これはファイル名ではなくファイル記述子として解釈されます。すなわち、{{トークン}}の展開結果はファイル記述子を表す非負整数となる必要があります。 パイプリダイレクトはパイプを開きます。標準出力 (演算子 +>>|+ の直前に非負整数を指定している場合はその値のファイル記述子) がパイプに書きこむためのファイル記述子になります。また{{トークン}}の展開結果で示されたファイル記述子がパイプから読み込むためのファイル記述子になります。 パイプリダイレクトは POSIX 規格にはない yash の独自拡張です。 [[process]] == プロセスリダイレクト プロセスリダイレクトを用いると別のコマンドの入力または出力を受け渡せるパイプを開くことができます。 プロセスリダイレクト:: +<({{サブコマンド}}…)+ + +>({{サブコマンド}}…)+ プロセスリダイレクトでは、{{サブコマンド}}が{zwsp}link:exec.html#subshell[サブシェル]で実行されます。このとき、+<({{サブコマンド}}…)+ の形式のプロセスリダイレクトでは、{{サブコマンド}}の標準出力がこのコマンドの標準入力に渡るようパイプが開かれます。+>({{サブコマンド}}…)+ の形式のプロセスリダイレクトでは、このコマンドの標準出力が{{サブコマンド}}の標準入力に渡るようパイプが開かれます。 プロセスリダイレクトは POSIX 規格にはない yash の独自拡張です。Bash と zsh にはプロセスリダイレクトと同様の構文を用いるプロセス置換という機能がありますが、プロセスリダイレクトとプロセス置換の挙動は異なっており、互換性はありません。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/syntax.txt000066400000000000000000000661631354143602500156610ustar00rootroot00000000000000= コマンドの文法 :encoding: UTF-8 :lang: ja //:title: Yash マニュアル - コマンドの文法 :description: Yash のコマンドの文法とその意味論の説明 シェルはコマンドを一行ずつ読み込んで解釈し、実行します。一行に複数のコマンドがある場合は、それら全てを解釈してから実行します。一つのコマンドが複数行にまたがっている場合は、そのコマンドを解釈し終えるのに必要なだけ後続の行が読み込まれます。コマンドを正しく解釈できない場合は、文法エラーとなり、コマンドは実行されません。 非{zwsp}link:interact.html[対話モード]で文法エラーが発生した時は、シェルはコマンドの読み込みを中止するため、それ以降のコマンドは一切読み込まれません。 [[tokens]] == トークンの解析と予約語 コマンドは、いくつかのトークンによって構成されます。dfn:[トークン]とは、シェルの文法における一つ一つの単語のことを言います。トークンは原則として空白 (空白文字またはタブ文字) によって区切られます。ただしコマンド置換などに含まれる空白はトークンの区切りとは見なしません。 以下の記号は、シェルの文法において特別な意味を持っています。これらの記号も多くの場合他の通常のトークンの区切りとなります。 ; & | < > ( ) [newline] 以下の記号はトークンの区切りにはなりませんが、文法上特別な意味を持っています。 $ ` \ " ' * ? [ # ~ = % 以下のトークンは特定の場面においてdfn:[予約語]と見なされます。予約語は複合コマンドなどを構成する一部となります。 ! { } [[ case do done elif else esac fi for function if in then until while これらのトークンは以下の場面において予約語となります。 * それがコマンドの最初のトークンのとき * それが他の予約語 (+case+, +for+, +in+ を除く) の直後のトークンのとき * それがコマンドの最初のトークンではないが、複合コマンドの中で予約語として扱われるべきトークンであるとき トークンが `#` で始まる場合、その `#` から行末まではdfn:[コメント]と見なされます。コマンドの解釈においてコメントは完全に無視されます。 [[quotes]] == クォート 空白や上記の区切り記号・予約語などを通常の文字と同じように扱うには、適切な引用符でクォートする必要があります。引用符は、それ自体をクォートしない限り通常の文字としては扱われません。シェルでは以下の三種類の引用符が使えます。 * バックスラッシュ (+\+) は直後の一文字をクォートします。 + 例外として、バックスラッシュの直後に改行がある場合、それは改行をクォートしているのではなく、dfn:[行の連結]と見なされます。バックスラッシュと改行が削除され、バックスラッシュがあった行とその次の行が元々一つの行であったかのように扱われます。 * 二つの一重引用符 (+'+) で囲んだ部分では、全ての文字は通常の文字と同じように扱われます。改行を一重引用符で囲むこともできます。ただし、一重引用符を一重引用符で囲むことはできません。 * 二つの二重引用符 (+"+) で囲んだ部分も一重引用符で囲んだ部分と同様にクォートされますが、いくつか例外があります。二重引用符で囲んだ部分では、パラメータ展開・コマンド置換・数式展開が通常通り解釈されます。またバックスラッシュは +$+, +`+, +"+, +\+ の直前にある場合および行の連結を行う場合にのみ引用符として扱われ、それ以外のバックスラッシュは通常の文字と同様に扱われます。 [[aliases]] == エイリアス コマンドを構成する各トークンは、それが予め登録されたエイリアスの名前に一致するかどうか調べられます。一致するものがあれば、そのトークンはそのエイリアスの内容に置き換えられて、その後コマンドの解析が続けられます。これをdfn:[エイリアス置換]といいます。 エイリアスの名前に引用符を含めることはできないので、引用符を含むトークンはエイリアス置換されません。また、予約語やコマンドを区切る記号もエイリアス置換されません。 エイリアスには通常のエイリアスとグローバルエイリアスの二種類があります。dfn:[通常のエイリアス]は、コマンドの最初のトークンにのみ一致します。dfn:[グローバルエイリアス]はコマンド内の全てのトークンが一致の対象です。グローバルエイリアスは POSIX 規格にはない拡張機能です。 通常のエイリアスで置換された部分の最後の文字が空白の場合、特例としてその直後のトークンにも通常のエイリアスの置換が行われます。 エイリアス置換の結果がさらに別のエイリアスに一致して置換される場合もあります。しかし、同じエイリアスに再び一致することはありません。 エイリアスを登録するには link:_alias.html[alias 組込みコマンド]を、登録を削除するには link:_unalias.html[unalias 組込みコマンド]を使用します。 [[simple]] == 単純コマンド 最初のトークンが予約語でないコマンドは、dfn:[単純コマンド]です。単純コマンドは{zwsp}link:exec.html#simple[単純コマンドの実行]のしかたに従って実行されます。 単純コマンドの初めのトークンが {{名前}}={{値}} の形式になっている場合は、それは{zwsp}link:params.html#variables[変数]代入と見なされます。ただしここでの{{名前}}は、一文字以上のアルファベット・数字または下線 (+_+) で、かつ最初が数字でないものです。変数代入ではない最初のトークンはコマンドの名前と解釈されます。それ以降のトークンは (たとえ変数代入の形式をしていたとしても) コマンドの引数と解釈されます。 {{名前}}=({{トークン列}}) の形になっている変数代入は、{zwsp}link:params.html#arrays[配列]の代入となります。括弧内には任意の個数のトークンを書くことができます。またこれらのトークンは空白・タブだけでなく改行で区切ることもできます。 [[pipelines]] == パイプライン dfn:[パイプライン]は、一つ以上のコマンド (<>、<>、または<>) を記号 +|+ で繋いだものです。 二つ以上のコマンドからなるパイプラインの実行は、パイプラインに含まれる各コマンドをそれぞれ独立した{zwsp}link:exec.html#subshell[サブシェル]で同時に実行することで行われます。この時、各コマンドの標準出力は次のコマンドの標準入力にパイプで受け渡されます。最初のコマンドの標準入力と最後のコマンドの標準出力は元のままです。 link:_set.html#so-pipefail[Pipe-fail オプション]が無効な時は、最後のコマンドの終了ステータスがパイプラインの終了ステータスになります。有効な時は、終了ステータスが 0 でなかった最後のコマンドの終了ステータスがパイプラインの終了ステータスになります。全てのコマンドの終了ステータスが 0 だった時は、パイプラインの終了ステータスも 0 になります。 パイプラインの先頭には、記号 +!+ を付けることができます。この場合、パイプラインの終了ステータスが__逆転__します。つまり、最後のコマンドの終了ステータスが 0 のときはパイプラインの終了ステータスは 1 になり、それ以外の場合は 0 になります。 Korn シェルでは構文 +!(...)+ は POSIX で定義されていない独自のパス名展開パターンと見做されます。{zwsp}link:posix.html[POSIX 準拠モード]では +!+ と +(+ の二つのトークンは一つ以上の空白で区切る必要があります。 [NOTE] 最後のコマンドの終了ステータスがパイプラインの終了ステータスになるため、パイプラインの実行が終了するのは少なくとも最後のコマンドの実行が終了した後です。しかしそのとき他のコマンドの実行が終了しているとは限りません。また、最後のコマンドの実行が終了したらすぐにパイプラインの実行が終了するとも限りません。(シェルは、他のコマンドの実行が終わるまで待つ場合があります) [NOTE] POSIX 規格では、パイプライン内の各コマンドはサブシェルではなく現在のシェルで実行してもよいことになっています。 [[and-or]] == And/or リスト dfn:[And/or リスト]は一つ以上の<>を記号 +&&+ または +||+ で繋いだものです。 And/or リストの実行は、and/or リストに含まれる各パイプラインを条件付きで実行することで行われます。最初のパイプラインは常に実行されます。それ以降のパイプラインの実行は、前のパイプラインの終了ステータスによります。 - 二つのパイプラインが +&&+ で繋がれている場合、前のパイプラインの終了ステータスが 0 ならば後のパイプラインが実行されます。 - 二つのパイプラインが +||+ で繋がれている場合、前のパイプラインの終了ステータスが 0 でなければ後のパイプラインが実行されます。 - それ以外の場合は、and/or リストの実行はそこで終了し、それ以降のパイプラインは実行されません。 最後に実行したパイプラインの終了ステータスが and/or リストの終了ステータスになります。 構文上、and/or リストの直後には原則として記号 +;+ または +&+ が必要です (<>参照)。 [[async]] == コマンドの区切りと非同期コマンド シェルが受け取るコマンドの全体は、<>を +;+ または +&+ で区切ったものです。行末、 +;;+ または +)+ の直前にある +;+ は省略できますが、それ以外の場合は and/or リストの直後には必ず +;+ と +&+ のどちらかが必要です。 And/or リストの直後に +;+ がある場合は、その and/or リストは同期的に実行されます。すなわち、その and/or リストの実行が終わった後に次の and/or リストが実行されます。And/or リストの直後に +&+ がある場合は、その and/or リストは非同期的に実行されます。すなわち、その and/or リストの実行を開始した後、終了を待たずに、すぐさま次の and/or リストの実行に移ります。非同期な and/or リストは常に{zwsp}link:exec.html#subshell[サブシェル]で実行されます。また終了ステータスは常に 0 です。 link:job.html[ジョブ制御]を行っていないシェルにおける非同期な and/or リストでは、標準入力が自動的に /dev/null にリダイレクトされるとともに、SIGINT と SIGQUIT を受信したときの動作が ``無視'' に設定されこれらのシグナルを受けてもプログラムが終了しないようにします。 ジョブ制御を行っているかどうかにかかわらず、非同期コマンドを実行するとシェルはそのコマンドのプロセス ID を記憶します。{zwsp}link:params.html#sp-exclamation[特殊パラメータ +!+] を参照すると非同期コマンドのプロセス ID を知ることができます。非同期コマンドの状態や終了ステータスは link:_jobs.html[jobs] や link:_wait.html[wait] 組込みコマンドで知ることができます。 [[compound]] == 複合コマンド 複合コマンドは、より複雑なプログラムの制御を行う手段を提供します。 [[grouping]] === グルーピング グルーピングを使うと、複数のコマンドを一つのコマンドとして扱うことができます。 通常のグルーピングの構文:: +{ {{コマンド}}...; }+ サブシェルのグルーピングの構文:: +({{コマンド}}...)+ +{+ と +}+ は予約語なので、他のコマンドのトークンとくっつけて書いてはいけません。一方 +(+ と +)+ は特殊な区切り記号と見なされるので、他のトークンとくっつけて書くことができます。 通常のグルーピング構文 (+{+ と +}+ で囲む) では、コマンドは (他のコマンドと同様に) 現在のシェルで実行されます。サブシェルのグルーピング構文 (+(+ と +)+ で囲む) では、括弧内のコマンドは新たな{zwsp}link:exec.html#subshell[サブシェル]で実行されます。 link:posix.html[POSIX 準拠モード]では括弧内に少なくとも一つのコマンドが必要ですが、非 POSIX 準拠モードではコマンドは一つもなくても構いません。 グルーピングの終了ステータスは、グルーピングの中で実行された最後のコマンドの終了ステータスです。グルーピング内にコマンドが一つもない場合、グルーピングの終了ステータスはグルーピングの直前に実行されたコマンドの終了ステータスになります。 [[if]] === If 文 If 文は条件分岐を行います。分岐の複雑さに応じていくつか構文のバリエーションがあります。 If 文の基本構文:: +if {{条件コマンド}}...; then {{内容コマンド}}...; fi+ Else がある場合:: +if {{条件コマンド}}...; then {{内容コマンド}}...; else {{内容コマンド}}...; fi+ Elif がある場合:: +if {{条件コマンド}}...; then {{内容コマンド}}...; elif {{条件コマンド}}...; then {{内容コマンド}}...; fi+ Elif と else がある場合:: +if {{条件コマンド}}...; then {{内容コマンド}}...; elif {{条件コマンド}}...; then {{内容コマンド}}...; else {{内容コマンド}}...; fi+ If 文の実行では、どの構文の場合でも、+if+ の直後にある{{条件コマンド}}がまず実行されます。条件コマンドの終了ステータスが 0 ならば、条件が真であると見なされて +then+ の直後にある{{内容コマンド}}が実行され、if 文の実行はそれで終了します。終了ステータスが 0 でなければ、条件が偽であると見なされます。ここで +else+ も +elif+ もなければ、if 文の実行はこれで終わりです。+else+ がある場合は、+else+ の直後の{{内容コマンド}}が実行されます。+elif+ がある場合は、+elif+ の直後の{{条件コマンド}}が実行され、その終了ステータスが 0 であるかどうか判定されます。その後は先程と同様に条件分岐を行います。 +elif …; then …;+ は一つの if 文内に複数あっても構いません。 If 文全体の終了ステータスは、実行された内容コマンドの終了ステータスです。内容コマンドが実行されなかった場合 (どの条件も偽で、+else+ がない場合) は 0 です。 [[while-until]] === While および until ループ While ループと until ループは単純なループ構文です。 While ループの構文:: +while {{条件コマンド}}...; do {{内容コマンド}}...; done+ Until ループの構文:: +until {{条件コマンド}}...; do {{内容コマンド}}...; done+ 非 link:posix.html[POSIX 準拠モード]では +{{条件コマンド}}…;+ および +{{内容コマンド}}…;+ は省略可能です。 While ループの実行ではまず{{条件コマンド}}が実行されます。そのコマンドの終了ステータスが 0 ならば、{{内容コマンド}}が実行されたのち、再び{{条件コマンド}}の実行に戻ります。この繰り返しは{{条件コマンド}}の終了ステータスが 0 でなくなるまで続きます。 [NOTE] {{条件コマンド}}の終了ステータスが最初から 0 でないときは、{{内容コマンド}}は一度も実行されません。 Until ループは、ループを続行する条件が逆になっている以外は while ループと同じです。すなわち、{{条件コマンド}}の終了ステータスが 0 でなければ{{内容コマンド}}が実行されます。 While/until ループ全体の終了ステータスは、最後に実行した{{内容コマンド}}の終了ステータスです。({{内容コマンド}}が存在しないか、一度も実行されなかったときは 0) [[for]] === For ループ For ループは指定されたそれぞれの単語について同じコマンドを実行します。 For ループの構文:: +for {{変数名}} in {{単語}}...; do {{コマンド}}...; done+ + +for {{変数名}} do {{コマンド}}...; done+ +in+ の直後の{{単語}}は一つもなくても構いませんが、+do+ の直前の +;+ (または改行) は必要です。これらの単語トークンは予約語としては認識されませんが、+&+ などの記号を含めるには適切な<>が必要です。非 link:posix.html[POSIX 準拠モード]では +{{コマンド}}…;+ がなくても構いません。 POSIX 準拠モードでは、{{変数名}}はポータブルな (すなわち ASCII 文字のみからなる) 名前でなければなりません。 For ループの実行ではまず{{単語}}が<>実行時の単語の展開と同様に展開されます (+in …;+ がない構文を使用している場合は、+in "$@";+ が省略されているものと見なされます)。続いて、展開で生成されたそれぞれの単語について順番に一度ずつ以下の処理を行います。 . 単語を{{変数名}}で指定した変数に代入する . {{コマンド}}を実行する 単語は{zwsp}link:exec.html#localvar[ローカル変数]として代入されます (link:posix.html[POSIX 準拠モード]が有効なときまたは link:_set.html#so-forlocal[for-local オプション]が無効なときを除く)。 展開の結果単語が一つも生成されなかった場合は、変数は作られず{{コマンド}}も一切実行されません。 For ループ全体の終了ステータスは、最後に実行した{{コマンド}}の終了ステータスです。{{コマンド}}があるのに一度も実行されなかったときは 0 です。{{コマンド}}がない場合、for ループの終了ステータスは for ループの一つ前に実行されたコマンドの終了ステータスになります。 変数が読み取り専用の場合、for ループの実行は 0 でない終了ステータスで中断されます。 [[case]] === Case 文 Case 文は単語に対してパターンマッチングを行い、その結果に対応するコマンドを実行します。 Case 文の構文:: +case {{単語}} in {{caseitem}}... esac+ Caseitem の構文:: +({{パターン}}) {{コマンド}}...;;+ +case+ と +in+ の間の単語はちょうど一トークンでなければなりません。この単語トークンは予約語としては認識されませんが、+&+ などの記号を含めるには適切な<>が必要です。+in+ と +esac+ の間には任意の個数の caseitem を置きます (0 個でもよい)。Caseitem の最初の +(+ と +esac+ の直前の +;;+ は省略できます。また{{コマンド}}が +;+ で終わる場合はその +;+ も省略できます。Caseitem の +)+ と +;;+ との間に{{コマンド}}が一つもなくても構いません。 Caseitem の{{パターン}}にはトークンを指定します。各トークンを +|+ で区切ることで複数のトークンをパターンとして指定することもできます。 Case 文の実行では、まず{{単語}}が{zwsp}link:expand.html[四種展開]されます。その後、各 caseitem に対して順に以下の動作を行います。 . {{パターン}}トークンを{{単語}}と同様に展開し、展開したパターンが展開した単語にマッチするかどうか調べます (link:pattern.html[パターンマッチング記法]参照)。{{パターン}}として指定されたトークンが複数ある場合はそれら各トークンに対してマッチするかどうか調べます (どれかのパターントークンがマッチしたらそれ以降のパターントークンは展開されません。Yash はトークンが書かれている順番にマッチするかどうかを調べますが、他のシェルもこの順序で調べるとは限りません)。 . マッチした場合は、直後の{{コマンド}}を実行し、それでこの case 文の実行は終了です。マッチしなかった場合は、次の caseitem の処理に移ります。 Case 文全体の終了ステータスは、実行した{{コマンド}}の終了ステータスです。{{コマンド}}が実行されなかった場合 (どのパターンもマッチしなかったか、caseitem が一つもないか、マッチしたパターンの後にコマンドがない場合) は、終了ステータスは 0 です。 link:posix.html[POSIX 準拠モード]では、(+|+ で区切られた最初の) {{パターン}}トークンを +esac+ にすることはできません。 [[double-bracket]] === 二重ブラケットコマンド dfn:[二重ブラケットコマンド]は link:_test.html[test コマンド]に近い動作をする構文です。ブラケットに囲まれた式を展開して評価します。 二重ブラケットコマンド構文:: +[[ {{式}} ]]+ {{式}}は単一の原子式とすることも原子式や演算子の組み合わせとすることもできます。式の構文解析は、コマンドの実行時ではなく構文解析時に行われます。演算子 (原子式の演算子もそうでないものも) は{zwsp}link:#quotes[クォート]してはなりません。クォートすると通常の単語と見なされます。 コマンドが実行されるとき、被演算子となる単語は{zwsp}link:expand.html[四種展開]されます。ブレース展開・単語分割・パス名展開は行われません。 二重ブラケットコマンドでは test コマンドと同様に以下の原子式が使用できます: 単項原子式:: +-b+, +-c+, +-d+, +-e+, +-f+, +-G+, +-g+, +-h+, +-k+, +-L+, +-N+, +-n+, +-O+, +-o+, +-p+, +-r+, +-S+, +-s+, +-t+, +-u+, +-w+, +-x+, +-z+ 二項原子式:: +-ef+, +-eq+, +-ge+, +-gt+, +-le+, +-lt+, +-ne+, +-nt+, +-ot+, +-veq+, +-vge+, +-vgt+, +-vle+, +-vlt+, +-vne+, +===+, +!==+, +=~+, +<+, +>+ さらに、文字列を比較するための三つの二項原子式が使用できますが、test コマンドとは動作が異なります: +=+ および +==+ 原子式は右辺を{zwsp}link:pattern.html[パターン]として扱い、左辺がそれにマッチするかどうかを判定します。 +!=+ 原子式は同様の判定を行い、逆の結果を返します。 原子式の被演算子となる単語が +]]+ であるか他の演算子と紛らわしい場合は、クォートする必要があります。 [NOTE] 将来新しい種類の原子式が導入される可能性があります。ハイフンで始まる単語は全てクォートすることをお勧めします。 [NOTE] `<=` および `>=` 二項原子式は二重ブラケットコマンド内においては正しく構文解析できないため使用できません。 以下の演算子を使用して原子式を組み合わせることができます (ここでは演算子の結合順位が高い順に示します): +( {{式}} )+:: 式を括弧で囲むと演算子の優先順位を変更できます。 +! {{式}}+:: 感嘆符は式の結果を反転します。 +{{expression}} && {{expression}}+:: 二重アンパサンドは連言 (論理積) を表します。両辺が共に真である時、全体も真となります。左辺が先に展開・判定されます。左辺が真である場合のみ右辺が展開・判定されます。 +{{expression}} || {{expression}}+:: 二重縦棒は選言 (論理和) を表します。両辺が共に偽である時、全体も偽となります。左辺が先に展開・判定されます。左辺が偽である場合のみ右辺が展開・判定されます。 [NOTE] 二重ブラケットコマンドでは、test コマンドの様に +-a+ および +-o+ を連言・選言演算子として使用することはできません。 二重ブラケットコマンドの終了ステータスは、{{式}}が真ならば 0、偽ならば 1、展開エラーやその他の理由で判定が行えない場合は 2 です。 [NOTE] 二重ブラケットコマンドは bash, ksh, mksh, zsh にもありますが、POSIX にはない拡張機能です。シェルによって多少動作が異なります。移植性を高めるには二重ブラケットコマンドよりも test コマンドを使用することをお勧めします。 [[funcdef]] == 関数定義 関数定義コマンドは、{zwsp}link:exec.html#function[関数]を定義します。 関数定義コマンドの構文:: +{{関数名}} ( ) {{複合コマンド}}+ + +function {{関数名}} {{複合コマンド}}+ + +function {{関数名}} ( ) {{複合コマンド}}+ 予約語 +function+ を用いない一つ目の形式では、{{関数名}}には引用符などの特殊な記号を含めることはできません。予約語 +function+ を用いる二つ目または三つ目の形式では、{{関数名}}は実行時に{zwsp}link:expand.html[四種展開]されます。(link:posix.html[POSIX 準拠モード]では、予約語 +function+ を用いる形式の関数定義は使えません。また{{関数名}}はポータブルな (すなわち ASCII 文字のみからなる) 名前でなければなりません。) 関数定義コマンドを実行すると、指定した{{関数名}}の関数が{{複合コマンド}}を内容として定義されます。 関数定義コマンドに対して直接{zwsp}link:redir.html[リダイレクト]を行うことはできません。関数定義コマンドの最後にあるリダイレクトは、関数の内容である{{複合コマンド}}に対するリダイレクトと見なされます。例えば +func() { cat; } >/dev/null+ と書いた場合、リダイレクトされるのは +func() { cat; }+ ではなく +{ cat; }+ です。 関数定義コマンドの終了ステータスは、関数が正しく定義された場合は 0、そうでなければ非 0 です。 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/ja/yash.txt.in000066400000000000000000000005271354143602500156740ustar00rootroot00000000000000= YASH(1) 裕貴 渡邊 v{yashversion}, :encoding: UTF-8 :lang: ja == 名前 yash - POSIX 準拠コマンドラインシェル == 書式 +yash [オプション...] [--] [オペランド...]+ :leveloffset: 1 // (Replaced with generated contents list) // :leveloffset: 0 // vim: set filetype=asciidoc expandtab: yash-2.49/doc/job.txt000066400000000000000000000121471354143602500145040ustar00rootroot00000000000000= Job control :encoding: UTF-8 :lang: en //:title: Yash manual - Job control :description: This page describes yash's job control feature. dfn:[Job control] is a function of the shell that executes multiple commands simultaneously and suspends/resumes the commands. When job control is active: - Every link:syntax.html#pipelines[pipeline] executed by the shell becomes a dfn:[job]. A job has its unique process group ID that is shared among all processes in the job. - If the processes of a job are suspended while the shell is waiting for the processes to finish, the shell continues to the next command as if the process have finished. The shell remembers the job as suspended so that it can be resumed later. - If a job is executed link:syntax.html#async[synchronously], the shell sets the foreground process group of the terminal to the process group of the job. When the job is finished (or suspended), the shell gets back to the foreground. - The link:exec.html#subshell[subshell] executing a link:expand.html#cmdsub[command substitution] has its own unique process group ID like a job. However, the shell does not remember the subshell as a job, so it cannot be suspended or resumed. - If the shell is link:interact.html[interactive], job status is reported before every command line prompt as if the command +link:_jobs.html[jobs] -n+ is executed. - The standard input of an link:syntax.html#async[asynchronous command] is not automatically redirected to /dev/null. - The shell does not exit when it receives the SIGTSTP signal. - The value of the link:params.html#sp-hyphen[+-+ special parameter] contains +m+. - When a job finished for which the link:_wait.html[wait built-in] has been waiting, the fact is reported (only if the shell is link:interact.html[interactive] and not in the link:posix.html[POSIXly-correct mode]). When job control is inactive, processes executed by the shell have the same process group ID as the shell. The shell treats link:syntax.html#async[asynchronous commands] as an uncontrolled job. You can use the following built-ins to manipulate jobs: link:_jobs.html[jobs]:: prints existing jobs link:_fg.html[fg] and link:_bg.html[bg]:: run jobs in the foreground or background link:_wait.html[wait]:: waits for jobs to be finished (or suspended) link:_disown.html[disown]:: forgets jobs link:_kill.html[kill]:: sends a signal to jobs An interactive job-controlling shell reports jobs status before every prompt by default. You can set the following options to make the shell report status at other timings: link:_set.html#so-notify[notify]:: the shell reports immediately whenever job status changes. link:_set.html#so-notifyle[notify-le]:: the shell reports immediately when job status changes while link:lineedit.html[line-editing]. A job is removed from the shell's job list when: - it has finished and the link:_jobs.html[jobs built-in] reported it, - the link:_wait.html[wait built-in] successfully waited for the job to finish, or - the link:_disown.html[disown built-in] removed the job. Jobs are not removed from the list when an interactive shell automatically reports the status of jobs. [NOTE] The word ``stop'' is synonymous to ``suspend'' in the context of job control. [[jobid]] == Job ID Some link:builtin.html[built-in]s use the following notation, which is called dfn:[job ID], to specify a job to operate on: +%+:: +%%+:: +%++:: the current job +%-+:: the previous job +%{{n}}+:: the job that has job number {{n}}, where {{n}} is a positive integer +%{{string}}+:: the job whose name begins with {{string}} +%?{{string}}+:: the job whose name contains {{string}} The dfn:[current job] and dfn:[previous job] are jobs selected by the shell according to the following rules: - When there is one or more suspended jobs, the current job is selected from them. - When there is one or more suspended jobs other than the current job, the previous job is selected from them. - The current and previous jobs are always different. When the shell has only one job, it is the current job and there is no previous job. - When the current job finished, the previous job becomes the current job. - When the current job is changed, the old current job becomes the previous job except when the old job finished. - When the foreground job is suspended, the job becomes the current job. Yash has some options to modify the rules of the current/previous job selection. (The rules above have priority over the options below.) link:_set.html#so-curasync[cur-async]:: When a new link:syntax.html#async[asynchronous command] is started, it becomes the current job. link:_set.html#so-curbg[cur-bg]:: When a job is resumed by the link:_bg.html[bg built-in], the job becomes the current job. link:_set.html#so-curstop[cur-stop]:: When a job is suspended, it becomes the current job. The current and previous jobs are not changed as long as the rules above are met. The rules of the current/previous job selection defined in the POSIX standard are looser than yash's rules above. Other POSIX-compliant shells may select the current and previous jobs differently. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/lineedit.txt000066400000000000000000001431031354143602500155240ustar00rootroot00000000000000= Line-editing :encoding: UTF-8 :lang: en //:title: Yash manual - Line-editing :description: This page describes yash's line-editing feature. With the dfn:[line-editing] feature, you can edit the command text when you input a command to an link:interact.html[interactive shell]. It not only works as a simple visual-interface editor, but also is integrated with the link:interact.html#history[command history]. You can recall, edit, and execute commands in the history with line-editing instead of using the link:_fc.html[fc built-in]. Line-editing has two editing modes, the vi and emacs modes, which each have their own key binding settings. By switching editing modes, you can change key bindings used in line-editing. Each mode has a corresponding link:_set.html#options[shell option], which determines whether the mode is currently active or not. No more than one mode can be active at a time, so the options for the other modes are automatically turned off when you turn on the option for one mode. The whole line-editing feature is deactivated when those options are off. When an interactive shell is started, the vi mode is automatically activated if the standard input and error are both connected to a terminal. Line-editing can be used only when the standard input and error are both connected to a terminal. If not, the shell silently falls back to the normal input mechanism. While line-editing is being used, the shell uses the termios interface to change I/O settings of the terminal and the terminfo interface to parse input key sequences. [[options]] == Shell options on line-editing The following options can be set by the link:_set.html[set built-in] to enable line-editing and choose an editing mode to activate: link:_set.html#so-vi[vi]:: activates the vi mode. link:_set.html#so-emacs[emacs]:: activates the emacs mode. The other line-editing-related options are: link:_set.html#so-lealwaysrp[le-always-rp]:: When this options is enabled, the right prompt is always visible: when the cursor reaches the right prompt, it moves to the next line from the original position, which would otherwise be overwritten by input text. link:_set.html#so-lecompdebug[le-comp-debug]:: When enabled, internal information is printed during <>, which will help debugging completion scripts. link:_set.html#so-leconvmeta[le-conv-meta]:: When enabled, the 8th bit of each input byte is always treated as a meta-key flag, regardless of terminfo data. link:_set.html#so-lenoconvmeta[le-no-conv-meta]:: When enabled, the 8th bit of each input byte is never treated as a meta-key flag, regardless of terminfo data. + The le-conv-meta and le-no-conv-meta options cannot be both enabled at a time. When either is enabled, the other is automatically disabled. When neither is enabled, the 8th bit may be treated as a meta-key flag depending on terminfo data. link:_set.html#so-lepredict[le-predict]:: activates <>. link:_set.html#so-lepredictempty[le-predict-empty]:: When enabled, and <> is active, suggestions are also provided for empty input lines. link:_set.html#so-lepromptsp[le-prompt-sp]:: When enabled, the shell prints a special character sequence before printing each prompt so that every prompt is printed at the beginning of a line. + This option is enabled by default. link:_set.html#so-levisiblebell[le-visible-bell]:: When enabled, the shell flashes the terminal instead of sounding an alarm when an alert is required. [[modes]] == Editing modes The dfn:[vi mode] is an editing mode that offers key bindings similar to that of the vi editor. The vi mode has two sub-modes that are switched during editing: the insert and command modes. The sub-mode is always reset to the insert mode when line-editing is started for a new command line. In the insert mode, most characters are inserted to the buffer as typed. In the command mode, input characters are treated as commands that move the cursor, insert/delete text, etc. The dfn:[emacs mode] offers key bindings similar to the emacs editor. Most characters are inserted to the buffer as typed, but more characters are treated as commands than the vi insert mode. Another sub-mode is used while you enter search keywords. The sub-mode is called the dfn:[search mode], which offers slightly different key bindings depending on the active editing mode. [[commands]] == Line-editing commands All characters the user enters while line-editing is active are treated as line-editing commands listed below. The link:_bindkey.html[bindkey built-in] allows customizing the key bindings of each mode (except for the search mode). The list below shows not only the functions of commands but also the default key bindings. The keywords ``vi-insert'', ``vi-command'', ``vi-search'', ``emacs'', ``emacs-search'' means the vi insert mode, the vi command mode, the search mode for the vi mode (the vi search mode), the emacs mode, and the search mode for the emacs mode (the emacs search mode), respectively. Some commands take an argument that affects the function of the commands. For example, the forward-char command moves the cursor by as many characters as specified by the argument. To specify an argument, use the digit-argument command just before another command that takes an argument. [[basic-commands]] === Basic editing commands noop:: Do nothing. + -- vi-command:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] -- alert:: Alert. self-insert:: Insert the input character at the current cursor position. Characters escaped by <> cannot be inserted. + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- insert-tab:: Insert a tab character at the current cursor position. + -- emacs:: ifdef::basebackend-html[+++\^[\^I+++] ifndef::basebackend-html[`\^[\^I`] -- expect-verbatim:: Insert a character that is entered just after this command at the current cursor position. This command can input a character that cannot be input by the self-insert command, except a null character (`'\0'`). + -- vi-insert:: vi-search:: emacs-search:: ifdef::basebackend-html[+++\^V+++] ifndef::basebackend-html[`\^V`] emacs:: ifdef::basebackend-html[] +++\^Q, \^V+++ endif::basebackend-html[] ifndef::basebackend-html[`\^Q`, `\^V`] -- digit-argument:: Pass the input digit to the next command as an argument. + This command can be bound to a digit or hyphen. To pass ``12'' as an argument to the forward-char command in the vi mode, for example, enter `12l`. + -- vi-command:: ifdef::basebackend-html[] +++1, 2, 3, 4, 5, 6, 7, 8, 9+++ endif::basebackend-html[] ifndef::basebackend-html[`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`] emacs:: ifdef::basebackend-html[] +++\^[0, \^[1, \^[2, \^[3, \^[4, \^[5, \^[6, \^[7, \^[8, \^[9, \^[-+++ endif::basebackend-html[] ifndef::basebackend-html[] `\^[0`, `\^[1`, `\^[2`, `\^[3`, `\^[4`, `\^[5`, `\^[6`, `\^[7`, `\^[8`, `\^[9`, `\^[-`, endif::basebackend-html[] -- bol-or-digit:: Like the beginning-of-line command if there is no argument; like the digit-argument command otherwise. + -- vi-command:: ifdef::basebackend-html[+++0+++] ifndef::basebackend-html[`0`] -- accept-line:: Finish editing the current line. A newline is automatically appended to the line. The line will be executed by the shell. + If <> is active, the current prediction (if any) is ignored. See also the accept-prediction command. + -- vi-insert:: vi-command:: emacs:: emacs-search:: ifdef::basebackend-html[] +++\^J, \^M+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^M`] -- abort-line:: Abandon the current buffer and finish editing as if an empty line was input. + -- vi-insert:: vi-command:: vi-search:: emacs:: emacs-search:: ifdef::basebackend-html[] +++\!, \^C+++ endif::basebackend-html[] ifndef::basebackend-html[`\!`, `\^C`] -- eof:: Abandon the current buffer and finish editing as if the shell reached the end of input. This normally makes the shell exit. eof-if-empty:: Like the eof command if the buffer is empty; like the alert command otherwise. + -- vi-insert:: vi-command:: ifdef::basebackend-html[] +++\#, \^D+++ endif::basebackend-html[] ifndef::basebackend-html[`\#`, `\^D`] -- eof-or-delete:: Like the eof command if the buffer is empty; like the delete-char command otherwise. + -- emacs:: ifdef::basebackend-html[] +++\#, \^D+++ endif::basebackend-html[] ifndef::basebackend-html[`\#`, `\^D`] -- accept-with-hash:: If the current line does not begin with a hash sign (+#+) or there is no argument specified for this command, a hash sign is inserted at the beginning of the line. Otherwise, the beginning hash sign is removed from the line. Finally, the line is accepted like the accept-line command. + -- vi-command:: ifdef::basebackend-html[+++#+++] ifndef::basebackend-html[`#`] emacs:: ifdef::basebackend-html[+++\^[#+++] ifndef::basebackend-html[`\^[#`] -- accept-prediction:: Like the accept-line command, but include the <> part. setmode-viinsert:: Switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++i, \I+++] ifndef::basebackend-html[`i`, `\I`] -- setmode-vicommand:: Switch to the vi command mode. + -- vi-insert:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] -- setmode-emacs:: Switch to the emacs mode. expect-char:: abort-expect-char:: These commands are not meant for use by the user. They are used by the shell to implement some other commands. redraw-all:: Reprint the prompt and the current line to the terminal. + -- vi-insert:: vi-command:: vi-search:: emacs:: emacs-search:: ifdef::basebackend-html[+++\^L+++] ifndef::basebackend-html[`\^L`] -- clear-and-redraw-all:: Clear the terminal and reprint the prompt and the current line. [[motion-commands]] === Motion commands dfn:[Motion commands] move the cursor on the line. Most motion commands accept an argument. When passed an argument, they repeat the cursor motion as many times as specified by the argument. Passing ``4'' as an argument to the forward-char command, for example, advances the cursor by four characters. -- The shell has several definitions of words as units of distance: A dfn:[bigword] is one or more adjacent non-whitespace characters. A dfn:[semiword] is one or more adjacent characters that contain no whitespaces or punctuations. An dfn:[emacsword] is one or more adjacent alphanumeric characters. A dfn:[viword] is either: - one or more adjacent alphanumeric characters and/or underscores (+_+), or - one or more adjacent characters that contain none of alphanumeric characters, underscores, and whitespaces. -- forward-char:: Move the cursor to the next character. + -- vi-insert:: ifdef::basebackend-html[+++\R+++] ifndef::basebackend-html[`\R`] vi-command:: ifdef::basebackend-html[] +++l, (a space), \R+++ endif::basebackend-html[] ifndef::basebackend-html[`l`, (space), `\R`] emacs:: ifdef::basebackend-html[] +++\R, \^F+++ endif::basebackend-html[] ifndef::basebackend-html[`\R`, `\^F`] -- backward-char:: Move the cursor to the previous character. + -- vi-insert:: ifdef::basebackend-html[+++\L+++] ifndef::basebackend-html[`\L`] vi-command:: ifdef::basebackend-html[] +++h, \B, \L, \?, \^H, +++ endif::basebackend-html[] ifndef::basebackend-html[`h`, `\B`, `\L`, `\?`, `\^H`] emacs:: ifdef::basebackend-html[] +++\L, \^B+++ endif::basebackend-html[] ifndef::basebackend-html[`\L`, `\^B`] -- forward-bigword:: Move the cursor to the next bigword. + -- vi-command:: ifdef::basebackend-html[+++W+++] ifndef::basebackend-html[`W`] -- end-of-bigword:: Move the cursor to the next end of a bigword. + -- vi-command:: ifdef::basebackend-html[+++E+++] ifndef::basebackend-html[`E`] -- backward-bigword:: Move the cursor to the previous bigword. + -- vi-command:: ifdef::basebackend-html[+++B+++] ifndef::basebackend-html[`B`] -- forward-semiword:: Move the cursor to the next semiword. end-of-semiword:: Move the cursor to the next end of a semiword. backward-semiword:: Move the cursor to the previous semiword. forward-viword:: Move the cursor to the next viword. + -- vi-command:: ifdef::basebackend-html[+++w+++] ifndef::basebackend-html[`w`] -- end-of-viword:: Move the cursor to the next end of a viword. + -- vi-command:: ifdef::basebackend-html[+++e+++] ifndef::basebackend-html[`e`] -- backward-viword:: Move the cursor to the previous viword. + -- vi-command:: ifdef::basebackend-html[+++b+++] ifndef::basebackend-html[`b`] -- forward-emacsword:: Move the cursor to the next emacsword. + -- emacs:: ifdef::basebackend-html[] +++\^[f, \^[F+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[f`, `\^[F`] -- backward-emacsword:: Move the cursor to the previous emacsword. + -- emacs:: ifdef::basebackend-html[] +++\^[b, \^[B+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[b`, `\^[B`] -- beginning-of-line:: Move the cursor to the beginning of the line. + -- vi-insert:: vi-command:: ifdef::basebackend-html[+++\H+++] ifndef::basebackend-html[`\H`] emacs:: ifdef::basebackend-html[] +++\H, \^A+++ endif::basebackend-html[] ifndef::basebackend-html[`\H`, `\^A`] -- end-of-line:: Move the cursor to the end of the line. + -- vi-insert:: ifdef::basebackend-html[+++\E+++] ifndef::basebackend-html[`\E`] vi-command:: ifdef::basebackend-html[+++$, \E+++] ifndef::basebackend-html[`$`, `\E`] emacs:: ifdef::basebackend-html[] +++\E, \^E+++ endif::basebackend-html[] ifndef::basebackend-html[`\E`, `\^E`] -- go-to-column:: Move the cursor to the {{n}}th character on the line, where {{n}} is the argument. Assume {{n}} = 1 when no argument. + -- vi-command:: ifdef::basebackend-html[+++|+++] ifndef::basebackend-html[`|`] -- first-nonblank:: Move the cursor to the first non-blank character on the line. + -- vi-command:: ifdef::basebackend-html[+++^+++] ifndef::basebackend-html[`^`] -- find-char:: Move the cursor to the first position where a character that is entered just after this command appears after the current cursor position. + -- vi-command:: ifdef::basebackend-html[+++f+++] ifndef::basebackend-html[`f`] emacs:: ifdef::basebackend-html[+++\^\]+++] ifndef::basebackend-html[`\^\]`] -- find-char-rev:: Move the cursor to the last position where a character that is entered just after this command appears before the current cursor position. + -- vi-command:: ifdef::basebackend-html[+++F+++] ifndef::basebackend-html[`F`] emacs:: ifdef::basebackend-html[+++\^[\^\]+++] ifndef::basebackend-html[`\^[\^\]`] -- till-char:: Move the cursor to the first position just before a character that is entered just after this command appears after the current cursor position. + -- vi-command:: ifdef::basebackend-html[+++t+++] ifndef::basebackend-html[`t`] -- till-char-rev:: Move the cursor to the last position just after a character that is entered just after this command appears before the current cursor position. + -- vi-command:: ifdef::basebackend-html[+++T+++] ifndef::basebackend-html[`T`] -- refind-char:: Redo the last find-char, find-char-rev, till-char, till-char-rev command. + -- vi-command:: ifdef::basebackend-html[+++;+++] ifndef::basebackend-html[`;`] -- refind-char-rev:: Redo the last find-char, find-char-rev, till-char, till-char-rev command in the reverse direction. + -- vi-command:: ifdef::basebackend-html[+++,+++] ifndef::basebackend-html[`,`] -- [[editing-commands]] === Editing commands Editing commands modify contents of the buffer. Most editing commands accept an argument. When passed an argument, they repeat the modification as many times as specified by the argument. Texts deleted by commands whose name starts with ``kill'' are saved in dfn:[kill ring], from which deleted contents can be restored to the buffer. The most recent 32 texts are kept in the kill ring. delete-char:: Delete a character at the current cursor position if no argument is passed; like the kill-char command otherwise. + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\X+++] ifndef::basebackend-html[`\X`] -- delete-bigword:: Delete a bigword at the current cursor position if no argument is passed; like the kill-bigword command otherwise. delete-semiword:: Delete a semiword at the current cursor position if no argument is passed; like the kill-semiword command otherwise. delete-viword:: Delete a viword at the current cursor position if no argument is passed; like the kill-viword command otherwise. delete-emacsword:: Delete a emacsword at the current cursor position if no argument is passed; like the kill-emacsword command otherwise. backward-delete-char:: Delete a character just before the current cursor position if no argument is passed; like the backward-kill-char command otherwise. + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\B, \?, \^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\B`, `\?`, `\^H`] -- backward-delete-bigword:: Delete a bigword just before the current cursor position if no argument is passed; like the backward-kill-bigword command otherwise. backward-delete-semiword:: Delete a semiword just before the current cursor position if no argument is passed; like the backward-kill-semiword command otherwise. + -- vi-insert:: ifdef::basebackend-html[+++\^W+++] ifndef::basebackend-html[`\^W`] -- backward-delete-viword:: Delete a viword just before the current cursor position if no argument is passed; like the backward-kill-viword command otherwise. backward-delete-emacsword:: Delete a emacsword just before the current cursor position if no argument is passed; like the backward-kill-emacsword command otherwise. delete-line:: Delete the whole buffer contents. forward-delete-line:: Delete all characters from the current cursor position to the end of the buffer. backward-delete-line:: Delete all characters before the current cursor position. + -- vi-insert:: ifdef::basebackend-html[] +++\$, \^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`] -- kill-char:: Delete a character at the current cursor position and add it to the kill ring. + -- vi-command:: ifdef::basebackend-html[] +++x, \X+++ endif::basebackend-html[] ifndef::basebackend-html[`x`, `\X`] -- kill-bigword:: Delete a bigword at the current cursor position and add it to the kill ring. kill-semiword:: Delete a semiword at the current cursor position and add it to the kill ring. kill-viword:: Delete a viword at the current cursor position and add it to the kill ring. kill-emacsword:: Delete a emacsword at the current cursor position and add it to the kill ring. + -- emacs:: ifdef::basebackend-html[] +++\^[d, \^[D+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[d`, `\^[D`] -- backward-kill-char:: Delete a character just before the current cursor position and add it to the kill ring. + -- vi-command:: ifdef::basebackend-html[+++X+++] ifndef::basebackend-html[`X`] -- backward-kill-bigword:: Delete a bigword just before the current cursor position and add it to the kill ring. + -- emacs:: ifdef::basebackend-html[+++\^W+++] ifndef::basebackend-html[`\^W`] -- backward-kill-semiword:: Delete a semiword just before the current cursor position and add it to the kill ring. backward-kill-viword:: Delete a viword just before the current cursor position and add it to the kill ring. backward-kill-emacsword:: Delete a emacsword just before the current cursor position and add it to the kill ring. + -- emacs:: ifdef::basebackend-html[] +++\^[\B, \^[\?, \^[\^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[\B`, `\^[\?`, `\^[\^H`] -- kill-line:: Delete the whole buffer contents and add it to the kill ring. forward-kill-line:: Delete all characters from the current cursor position to the end of the buffer and add it to the kill ring. + -- emacs:: ifdef::basebackend-html[+++\^K+++] ifndef::basebackend-html[`\^K`] -- backward-kill-line:: Delete all characters before the current cursor position and add it to the kill ring. + -- emacs:: ifdef::basebackend-html[] +++\$, \^U, \^X\B, \^X\?+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`, `\^X\B`, `\^X\?`] -- put-before:: Insert the last-killed text before the current cursor position and move the cursor to the last character that was inserted. + -- vi-command:: ifdef::basebackend-html[+++P+++] ifndef::basebackend-html[`P`] -- put:: Insert the last-killed text after the current cursor position and move the cursor to the last character that was inserted. + -- vi-command:: ifdef::basebackend-html[+++p+++] ifndef::basebackend-html[`p`] -- put-left:: Insert the last-killed text before the current cursor position and move the cursor to the last character that was inserted. + -- emacs:: ifdef::basebackend-html[+++\^Y+++] ifndef::basebackend-html[`\^Y`] -- put-pop:: Replace the just put text with the next older killed text. + This command can be used only just after the put-before, put, put-left, or put-pop command. + -- emacs:: ifdef::basebackend-html[] +++\^[y, \^[Y+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[y`, `\^[Y`] -- undo:: Cancel modification by the last editing command. + -- vi:: ifdef::basebackend-html[+++u+++] ifndef::basebackend-html[`u`] emacs:: ifdef::basebackend-html[] +++\^_, \^X\$, \^X\^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\^_`, `\^X\$`, `\^X\^U`] -- undo-all:: Cancel all modification in the current buffer, restoring the initial contents. + -- vi:: ifdef::basebackend-html[+++U+++] ifndef::basebackend-html[`U`] emacs:: ifdef::basebackend-html[] +++\^[\^R, \^[r, \^[R+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[\^R`, `\^[r`, `\^[R`] -- cancel-undo:: Cancel cancellation by the last undo or undo-all command. + -- vi:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- cancel-undo-all:: Cancel all cancellation by all most recent undo and undo-all commands. redo:: Repeat modification by the last editing command. + -- vi-command:: ifdef::basebackend-html[+++.+++] ifndef::basebackend-html[`.`] -- [[completion-commands]] === Completion commands complete:: <> a word just before the cursor position and, if there is more than one candidate, show a list of the candidates. complete-next-candidate:: Like the complete command when candidates are not being listed; otherwise, select the next candidate in the list. + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\^I+++] ifndef::basebackend-html[`\^I`] -- complete-prev-candidate:: Like the complete command when candidates are not being listed; otherwise, select the previous candidate in the list. + -- vi-insert:: emacs:: ifdef::basebackend-html[+++\bt+++] ifndef::basebackend-html[`\bt`] -- complete-next-column:: Like the complete command when candidates are not being listed; otherwise, select the first candidate in the next column in the list. complete-prev-column:: Like the complete command when candidates are not being listed; otherwise, select the first candidate in the previous column in the list. complete-next-page:: Like the complete command when candidates are not being listed; otherwise, select the first candidate in the next page in the list. complete-prev-page:: Like the complete command when candidates are not being listed; otherwise, select the first candidate in the previous page in the list. complete-list:: Complete a word just before the cursor position. + If you pass no argument, a list of completion candidates is shown. Otherwise, the word is completed with the {{n}}th candidate where {{n}} is the argument. + -- emacs:: ifdef::basebackend-html[] +++\^[?, \^[=+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[?`, `\^[=`] -- complete-all:: Replace a word just before the cursor position with all possible completion candidates, each separated by a space. + -- emacs:: ifdef::basebackend-html[+++\^[*+++] ifndef::basebackend-html[`\^[*`] -- complete-max:: Complete a word just before the cursor position with the longest prefix of all possible completion candidates. complete-max-then-list:: Works like the complete-max command for the first use, then like the complete command when used successively. complete-max-then-next-candidate:: Works like the complete-max command for the first use, then like the complete-next-candidate command when used successively. complete-max-then-prev-candidate:: Works like the complete-max command for the first use, then like the complete-prev-candidate command when used successively. clear-candidates:: Clear the list of completion candidates. [[vi-commands]] === Vi-specific commands vi-replace-char:: Replace the character at the cursor position with a character that is entered just after this command. + -- vi-command:: ifdef::basebackend-html[+++r+++] ifndef::basebackend-html[`r`] -- vi-insert-beginning:: Move the cursor to the beginning of the line and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++I+++] ifndef::basebackend-html[`I`] -- vi-append:: Move the cursor to the next character and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++I+++] ifndef::basebackend-html[`I`] -- vi-append-to-eol:: Move the cursor to the end of the line and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++A+++] ifndef::basebackend-html[`A`] -- vi-replace:: Switch to the vi insert mode and start overwriting. While overwriting, the self-insert command replaces the character at cursor position rather than inserting a character. Overwriting ends when the editing mode is changed. + -- vi-command:: ifdef::basebackend-html[+++R+++] ifndef::basebackend-html[`R`] -- vi-switch-case:: Switch case of characters between the current and next cursor positions. This command must be followed by a motion command, which determines the next cursor position. vi-switch-case-char:: Switch case of the character at the current cursor position and move the cursor to the next character. + -- vi-command:: ifdef::basebackend-html[+++~+++] ifndef::basebackend-html[`~`] -- vi-yank:: Add to the kill ring the characters between the current and next cursor positions. This command must be followed by a motion command, which determines the next cursor position. + -- vi-command:: ifdef::basebackend-html[+++y+++] ifndef::basebackend-html[`y`] -- vi-yank-to-eol:: Add to the kill ring the characters from the current cursor position to the end of the line. + -- vi-command:: ifdef::basebackend-html[+++Y+++] ifndef::basebackend-html[`Y`] -- vi-delete:: Delete characters between the current and next cursor positions and add it to the kill ring. This command must be followed by a motion command, which determines the next cursor position. + -- vi-command:: ifdef::basebackend-html[+++d+++] ifndef::basebackend-html[`d`] -- vi-delete-to-eol:: Delete the characters from the current cursor position to the end of the line and add it to the kill ring. + -- vi-command:: ifdef::basebackend-html[+++D+++] ifndef::basebackend-html[`D`] -- vi-change:: Delete characters between the current and next cursor positions and switch to the vi insert mode. This command must be followed by a motion command, which determines the next cursor position. + -- vi-command:: ifdef::basebackend-html[+++c+++] ifndef::basebackend-html[`c`] -- vi-change-to-eol:: Delete the characters from the current cursor position to the end of the line and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++C+++] ifndef::basebackend-html[`C`] -- vi-change-line:: Delete the whole buffer contents and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++S+++] ifndef::basebackend-html[`S`] -- vi-yank-and-change:: Like the vi-change command, but the deleted text is added to the kill ring. vi-yank-and-change-to-eol:: Like the vi-change-to-eol command, but the deleted text is added to the kill ring. vi-yank-and-change-line:: Like the vi-change-line command, but the deleted text is added to the kill ring. vi-substitute:: Delete a character at the current cursor position, add it to the kill ring, and switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++s+++] ifndef::basebackend-html[`s`] -- vi-append-last-bigword:: Insert a space and the last bigword in the most recent command link:interact.html#history[history] entry just after the current cursor position and switch to the vi insert mode. If argument {{n}} is passed, the {{n}}th bigword in the entry is inserted instead of the last. + -- vi-command:: ifdef::basebackend-html[+++_+++] ifndef::basebackend-html[`_`] -- vi-exec-alias:: Execute the value of an link:syntax.html#aliases[alias] named +_{{c}}+ as editing commands where {{c}} is a character input just after this command. + -- vi-command:: ifdef::basebackend-html[+++@+++] ifndef::basebackend-html[`@`] -- vi-edit-and-accept:: Start the vi editor to edit the current buffer contents. When the editor finished, the edited buffer contents is accepted like the accept-line command unless the exit status of the editor is non-zero. + -- vi-command:: ifdef::basebackend-html[+++v+++] ifndef::basebackend-html[`v`] -- vi-complete-list:: Like the complete-list command, but also switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++=+++] ifndef::basebackend-html[`=`] -- vi-complete-all:: Like the complete-all command, but also switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++*+++] ifndef::basebackend-html[`*`] -- vi-complete-max:: Like the complete-max command, but also switch to the vi insert mode. + -- vi-command:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- vi-search-forward:: Switch to the vi search mode and start forward link:interact.html#history[history] search. + -- vi-command:: ifdef::basebackend-html[+++?+++] ifndef::basebackend-html[`?`] -- vi-search-backward:: Switch to the vi search mode and start backward link:interact.html#history[history] search. + -- vi-command:: ifdef::basebackend-html[+++/+++] ifndef::basebackend-html[`/`] -- [[emacs-commands]] === Emacs-specific commands emacs-transpose-chars:: Move a character just before the cursor to the right. + -- emacs:: ifdef::basebackend-html[+++\^T+++] ifndef::basebackend-html[`\^T`] -- emacs-transpose-words:: Move an emacsword just before the cursor to the right. + -- emacs:: ifdef::basebackend-html[] +++\^[t, \^[T+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[t`, `\^[T`] -- emacs-downcase-word:: Make an emacsword just after the cursor lowercase. + -- emacs:: ifdef::basebackend-html[] +++\^[l, \^[L+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[l`, `\^[L`] -- emacs-upcase-word:: Make an emacsword just after the cursor uppercase. + -- emacs:: ifdef::basebackend-html[] +++\^[u, \^[U+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[u`, `\^[U`] -- emacs-capitalize-word:: Capitalize the first letter of an emacsword just after the cursor. + -- emacs:: ifdef::basebackend-html[] +++\^[c, \^[C+++ endif::basebackend-html[] ifndef::basebackend-html[`\^[c`, `\^[u`] -- emacs-delete-horizontal-space:: Delete spaces around the cursor. If any argument was passed, delete spaces just before the cursor only. + -- emacs:: ifdef::basebackend-html[+++\^[\\+++] ifndef::basebackend-html[`\^[\\`] -- emacs-just-one-space:: Delete spaces around the cursor and leave one space. If an argument is specified, leave as many spaces as the argument. + -- emacs:: ifdef::basebackend-html[+++\^[ +++] ifndef::basebackend-html[`\^[`] (Escape followed by a space) -- emacs-search-forward:: Switch to the emacs search mode and start forward link:interact.html#history[history] search. + -- emacs:: ifdef::basebackend-html[+++\^S+++] ifndef::basebackend-html[`\^S`] -- emacs-search-backward:: Switch to the emacs search mode and start backward link:interact.html#history[history] search. + -- emacs:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- [[history-commands]] === History-related commands oldest-history:: Recall the oldest entry in the link:interact.html#history[history]. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. The cursor position remains unchanged. newest-history:: Recall the newest entry in the link:interact.html#history[history]. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. The cursor position remains unchanged. return-history:: Return to the initial buffer corresponding to none of existing link:interact.html#history[history] entries. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. The cursor position remains unchanged. oldest-history-bol:: Recall the oldest entry in the link:interact.html#history[history] and move the cursor to the beginning of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. + -- vi-command:: ifdef::basebackend-html[+++G+++] ifndef::basebackend-html[`G`] -- newest-history-bol:: Recall the newest entry in the link:interact.html#history[history] and move the cursor to the beginning of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. return-history-bol:: Return to the initial buffer corresponding to none of existing link:interact.html#history[history] entries and move the cursor to the beginning of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. + -- vi-command:: ifdef::basebackend-html[+++g+++] ifndef::basebackend-html[`g`] -- oldest-history-eol:: Recall the oldest entry in the link:interact.html#history[history] and move the cursor to the end of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. + -- emacs:: ifdef::basebackend-html[+++\^[<+++] ifndef::basebackend-html[`\^[<`] -- newest-history-eol:: Recall the newest entry in the link:interact.html#history[history] and move the cursor to the end of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. return-history-eol:: Return to the initial buffer corresponding to none of existing link:interact.html#history[history] entries and move the cursor to the end of the line. If argument {{n}} is passed, the entry whose number is {{n}} is recalled instead. + -- emacs:: ifdef::basebackend-html[+++\^[>+++] ifndef::basebackend-html[`\^[>`] -- next-history:: Recall the next link:interact.html#history[history] entry. The cursor position remains unchanged. prev-history:: Recall the previous link:interact.html#history[history] entry. The cursor position remains unchanged. next-history-bol:: Recall the next link:interact.html#history[history] entry and move the cursor to the beginning of the line. + -- vi-command:: ifdef::basebackend-html[] +++j, +, \D, \^N+++ endif::basebackend-html[] ifndef::basebackend-html[`j`, `+`, `\D`, `\^N`] -- prev-history-bol:: Recall the previous link:interact.html#history[history] entry and move the cursor to the beginning of the line. + -- vi-command:: ifdef::basebackend-html[] +++k, -, \U, \^P+++ endif::basebackend-html[] ifndef::basebackend-html[`k`, `-`, `\U`, `\^P`] -- next-history-eol:: Recall the next link:interact.html#history[history] entry and move the cursor to the end of the line. + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\D, \^N+++ endif::basebackend-html[] ifndef::basebackend-html[`\D`, `\^N`] -- prev-history-eol:: Recall the previous link:interact.html#history[history] entry and move the cursor to the end of the line. + -- vi-insert:: emacs:: ifdef::basebackend-html[] +++\U, \^P+++ endif::basebackend-html[] ifndef::basebackend-html[`\U`, `\^P`] -- search-again:: Repeat the last command history search. + -- vi-command:: ifdef::basebackend-html[+++n+++] ifndef::basebackend-html[`n`] -- search-again-rev:: Repeat the last command history search in the reverse direction. + -- vi-command:: ifdef::basebackend-html[+++N+++] ifndef::basebackend-html[`N`] -- search-again-forward:: Repeat the last command history search in the forward direction. search-again-backward:: Repeat the last command history search in the backward direction. beginning-search-forward:: Recall the next link:interact.html#history[history] entry that starts with the same text as the text from the beginning of the line up to the current cursor position. The cursor position remains unchanged. beginning-search-backward:: Recall the previous link:interact.html#history[history] entry that starts with the same text as the text from the beginning of the line up to the current cursor position. The cursor position remains unchanged. [[search-commands]] === Search mode commands srch-self-insert:: Insert the input character at the current cursor position. Characters escaped by <> cannot be inserted. + -- vi-search:: emacs-search:: ifdef::basebackend-html[+++\\+++] ifndef::basebackend-html[`\\`] -- srch-backward-delete-char:: Delete the last character in the search text. If the text is empty: + -- - like the srch-abort-search command when in the vi search mode, or - like the alert command when in the emacs search mode. -- + -- vi-search:: emacs-search:: ifdef::basebackend-html[] +++\B, \?, \^H+++ endif::basebackend-html[] ifndef::basebackend-html[`\B`, `\?`, `\^H`] -- srch-backward-delete-line:: Delete the whole search text. + -- vi-search:: emacs-search:: ifdef::basebackend-html[] +++\$, \^U+++ endif::basebackend-html[] ifndef::basebackend-html[`\$`, `\^U`] -- srch-continue-forward:: Find the next matching history entry. + -- emacs-search:: ifdef::basebackend-html[+++\^S+++] ifndef::basebackend-html[`\^S`] -- srch-continue-backward:: Find the previous matching history entry. + -- emacs-search:: ifdef::basebackend-html[+++\^R+++] ifndef::basebackend-html[`\^R`] -- srch-accept-search:: Finish the search mode, accepting the result being shown. + -- vi-search:: ifdef::basebackend-html[] +++\^J, \^M+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^M`] emacs-search:: ifdef::basebackend-html[] +++\^J, \^[+++ endif::basebackend-html[] ifndef::basebackend-html[`\^J`, `\^[`] -- srch-abort-search:: Abort search and restore the previous buffer contents. + -- vi-search:: ifdef::basebackend-html[+++\^[+++] ifndef::basebackend-html[`\^[`] emacs-search:: ifdef::basebackend-html[+++\^G+++] ifndef::basebackend-html[`\^G`] -- [[escape]] == Escape sequences In the link:_bindkey.html[bindkey built-in], escape sequences are used to represent special keys such as function keys and arrow keys. Every escape sequence starts with a backslash (`\`) and thus there is also an escape sequence for a backslash itself. Below are available escape sequences: `\\`:: Backslash (`\`) `\B`:: Backspace `\D`:: Down arrow `\E`:: End `\H`:: Home `\I`:: Insert (Insert-char, Enter-insert-mode) `\L`:: Left arrow `\N`:: Page-down (Next-page) `\P`:: Page-up (Previous-page) `\R`:: Right arrow `\U`:: Up arrow `\X`:: Delete `\!`:: INTR `\#`:: EOF `\$`:: KILL `\?`:: ERASE `\^@`:: Ctrl + @ `\^A`, `\^B`, ..., `\^Z`:: Ctrl + A, Ctrl + B, ..., Ctrl + Z + Note that Ctrl + I, Ctrl + J, and Ctrl + M are tab, newline, and carriage return, respectively. `\^[`:: Ctrl + [ (Escape) `\^\`:: Ctrl + \ `\^]`:: Ctrl + ] `\^^`:: Ctrl + ^ `\^_`:: Ctrl + _ `\^?`:: Ctrl + ? (Delete) `\F00`, `\F01`, ..., `\F63`:: F0, F1, ..., F63 `\a1`:: Top-left on keypad `\a3`:: Top-right on keypad `\b2`:: Center on keypad `\bg`:: Beginning `\bt`:: Back-tab `\c1`:: Bottom-left on keypad `\c3`:: Bottom-right on keypad `\ca`:: Clear-all-tabs `\cl`:: Close `\cn`:: Cancel `\co`:: Command `\cp`:: Copy `\cr`:: Create `\cs`:: Clear-screen or erase `\ct`:: Clear-tab `\dl`:: Delete-line `\ei`:: Exit-insert-mode `\el`:: Clear-to-end-of-line `\es`:: Clear-to-end-of-screen `\et`:: Enter (Send) `\ex`:: Exit `\fd`:: Find `\hp`:: Help `\il`:: Insert-line `\ll`:: Home-down `\me`:: Message `\mk`:: Mark `\ms`:: Mouse event `\mv`:: Move `\nx`:: Next-object `\on`:: Open `\op`:: Options `\pr`:: Print (Copy) `\pv`:: Previous-object `\rd`:: Redo `\re`:: Resume `\rf`:: Ref (Reference) `\rh`:: Refresh `\rp`:: Replace `\rs`:: Restart `\sf`:: Scroll-forward (Scroll-down) `\sl`:: Select `\sr`:: Scroll-backward (Scroll-up) `\st`:: Set-tab `\su`:: Suspend `\sv`:: Save `\ud`:: Undo `\SE`:: Shift + End `\SH`:: Shift + Home `\SI`:: Shift + Insert `\SL`:: Shift + Left arrow `\SR`:: Shift + Right arrow `\SX`:: Shift + Delete `\Sbg`:: Shift + Beginning `\Scn`:: Shift + Cancel `\Sco`:: Shift + Command `\Scp`:: Shift + Copy `\Scr`:: Shift + Create `\Sdl`:: Shift + Delete-line `\Sel`:: Shift + End-of-line `\Sex`:: Shift + Exit `\Sfd`:: Shift + Find `\Shp`:: Shift + Help `\Smg`:: Shift + Message `\Smv`:: Shift + Move `\Snx`:: Shift + Next `\Sop`:: Shift + Options `\Spr`:: Shift + Print `\Spv`:: Shift + Previous `\Srd`:: Shift + Redo `\Sre`:: Shift + Resume `\Srp`:: Shift + Replace `\Ssu`:: Shift + Suspend `\Ssv`:: Shift + Save `\Sud`:: Shift + Undo INTR, EOF, KILL, and ERASE are special characters configured by the stty command. In a typical configuration, they are sent by typing Ctrl+C, Ctrl+D, Ctrl+U, and Ctrl+H, respectively, but some configuration uses Ctrl+? instead of Ctrl+H for ERASE. [[completion]] == Command line completion By using the complete and complete-next-candidate commands, etc., you can complete command names, options, and operands. By default, the complete-next-candidate command is bound with the Tab key in the vi insert and emacs modes. Type a few first letters of a command name or pathname and hit the Tab key, and a list of matching names will be shown. You can choose a candidate from the list to complete the name by hitting the Tab key again. If there is only one matching name, no list will be shown and the name will directly be completed. If the name to be completed contains characters like `*` and `?`, it is treated as a link:pattern.html[pattern]. The name on the command line will be directly substituted with all possible names matching the pattern (you cannot choose from a list). Normally, command names are completed with command names and command arguments with pathnames. However, dfn:[completion functions] can be defined to refine completion results. [[completion-detail]] === Completion details When doing completion for the first time after the shell has been started, the INIT file is loaded as if the command string +link:_dot.html[.] -AL completion/INIT+ is executed. If the file is not found, it is silently ignored. This automatic loading is mainly intended for loading completion functions bundled with the shell, but you can let the shell load your own functions by putting a file in the link:params.html#sv-yash_loadpath[load path]. When completing a command name, the shell executes the +completion//command+ function and when completing a command argument, the +completion//argument+ function. If those completion functions are not defined, the shell just completes with command names or pathnames. When completing other names, such as the user name in link:expand.html#tilde[tilde expansion] and the parameter name in link:expand.html#params[parameter expansion], completion functions are never used: the shell just completes with user names, parameter names, or whatever applicable. Completion functions are link:exec.html#function[executed] without any arguments. The following link:exec.html#localvar[local variables] are automatically defined while executing completion functions: link:params.html#sv-ifs[+IFS+]:: The value is the three characters of a space, a tab, and a newline, which are the default value of the variable. +WORDS+:: This variable is an link:params.html#arrays[array] whose elements are a command name and arguments that have already been entered before the argument being completed. When completing a command name, the array has no elements. +TARGETWORD+:: The value is the partially entered command name or argument that is being completed. Completion candidates are generated by executing the link:_complete.html[complete built-in] during a completion function. Completion functions must not perform I/O to the terminal, or displayed text will be corrupted. Completion functions should run as quickly as possible for better user experience. While a completion function is being executed: - the link:posix.html[POSIXly-correct mode] and the link:_set.html#so-errreturn[err-return option] are temporarily disabled, - the link:_set.html#so-errexit[err-exit option] is temporarily ignored, and - link:_trap.html[traps] are not executed. [[prediction]] == Command line prediction This is an experimental feature. When the link:_set.html#so-lepredict[le-predict] option is enabled, the shell automatically tries to predict a command string fragment that follows the part of the command you have already typed in line-editing. For example, assume you have once typed the command +ls Documents+. Next time you start typing +ls Doc+, the shell will show +uments+ just after the cursor. If you are satisfied with this suggestion, you can move the cursor to the right by the forward-char or any other <> instead of typing the rest of the command. After moving the cursor after the last +s+, you can use the accept-line command to execute the command. You can also use the accept-prediction command to immediately execute the suggested command without moving the cursor. To distinguish the typed part and the predicted part of a command string, you can link:interact.html#prompt[change the font style] of the typed part by setting the link:params.html#sv-ps1s[+PS1S+] variable. Customizing the font style of the predicted part is not (yet) supported; it is always shown in the default style. When you move the cursor to the right, the predicted part up to the cursor becomes the typed part as if you actually typed it. Moving the cursor to the left does _not_ turn the typed part back to the prediction. Use deletion commands such as backward-delete-char to delete typed command fragment. The predicted part of the command is shown only when the cursor is at the end of the typed part. By default, the predicted part is shown after you start typing a command string. By enabling the link:_set.html#so-lepredictempty[le-predict-empty] option, the predicted part is also shown before you type the first character of the command string. The prediction algorithm suggests command fragments on the basis of the link:interact.html#history[command history]. The algorithm considers recent history entries more probable. It also takes command succession patterns into account. A predicted command fragment is not always a complete valid command because less probable part of the fragment is excluded from prediction. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/makeindex.sh000066400000000000000000000033131354143602500154650ustar00rootroot00000000000000# This script prints a section list for index.txt. # (C) 2012 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # skip up to the "//*//" marker while read -r line; do case $line in //*//) break;; *) printf '%s\n' "$line";; esac done # insert chapter/section titles for file do htmlfile=${file%.txt}.html # print the chapter title sed -n '1s/^=[[:space:]]*\(.*\)$/. link:'"${htmlfile}"'[\1]/p' <${file} # print section titles # The first sed prints something like this: # == # section-1[Section 1] # === # subsection-1-1[Subsection 1.1] # === # subsection-1-2[Subsection 1.2] # ... <${file} \ sed -n ' /^\[\[.*\]\]$/{ N /\n==/{ s/^\[\[\(.*\)\]\]\n\(===*\)[[:space:]]*\(.*\)$/\2\n\1[\3]/p } } ' | # The second sed replaces '=' with '.', inserts the "link:${htmlfile}#" # strings, and joins lines: # .. link:${htmlfile}#section-1[Section 1] # ... link:${htmlfile}#subsection-1-1[Subsection 1.1] # ... link:${htmlfile}#subsection-1-2[Subsection 1.2] # ... sed " s/=/./g N s|\n| link:${htmlfile}#| " done # print the rest of the input as is exec cat # vim: set ft=sh ts=8 sts=4 sw=4 noet tw=80: yash-2.49/doc/makeyash.sh000066400000000000000000000017501354143602500153250ustar00rootroot00000000000000# This script prints a section list for index.txt. # (C) 2012 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # skip up to the "//*//" marker while read -r line; do case $line in //*//) break;; *) printf '%s\n' "$line";; esac done # insert include macros printf 'include::%s[]\n\n' "$@" # print the rest of the input as is exec cat # vim: set ft=sh ts=8 sts=4 sw=4 noet tw=80: yash-2.49/doc/params.txt000066400000000000000000000437151354143602500152220ustar00rootroot00000000000000= Parameters and variables :encoding: UTF-8 :lang: en //:title: Yash manual - Parameters and variables :description: This page describes parameters and variables supported by yash. dfn:[Parameters] are string values that are expanded in link:expand.html[parameter expansion]. There are three types of parameters: <>, <> and <>. [[positional]] == Positional parameters dfn:[Positional parameters] are parameters that are identified by natural numbers. If there are three positional parameters, for example, they are identified as +1+, +2+, and +3+. You can obtain the number of positional parameters by <>. The +*+ and +@+ special parameters are expanded to all positional parameters. Positional parameters are initialized from the shell's command line arguments when the shell is started (see link:invoke.html#arguments[Command line arguments]). In the initialization, the order of the operands are preserved as the order of the positional parameters. When the shell executes a link:exec.html#function[function] call, positional parameters are changed to the arguments to the function call so that you can access the arguments while the function is being executed. Positional parameters are restored to the original values when the execution of the function is finished. Positional parameters can be manipulated by built-in commands like link:_set.html[set] and link:_shift.html[shift]. Note that +0+ is not a positional parameter but a special parameter. [[special]] == Special parameters dfn:[Special parameters] are parameters each identified by a single symbol. They cannot be directly assigned to by the user. Yash provides the following special parameters: [[sp-zero]]+0+:: The name of the shell executable file or the script file that was specified in the invocation of the shell. [[sp-hash]]+#+:: The number of current positional parameters. The value is a non-negative integer. [[sp-dollar]]+$+:: The process ID of the shell. The value is a positive integer and is never changed even in subshells. [[sp-hyphen]]+-+:: Currently enabled shell options. The value is a concatenation of alphabet characters that are the names of currently enabled single-character options that can be specified in shell invocation. The value reflects changes of enabled options when you enable or disable options using the link:_set.html[set built-in]. [[sp-question]]+?+:: The exit status of the last executed link:syntax.html#pipelines[pipeline]. The value is a non-negative integer. [[sp-exclamation]]+!+:: The process ID of the last executed link:syntax.html#async[asynchronous list]. [[sp-asterisk]]+*+:: This special parameter represents the whole <>. When there is no positional parameters, the value of this special parameter is the empty string. When there is more than one positional parameter, the value is a concatenation of all the positional parameters, each of which is separated as follows: + -- - If the <> variable exists and its value is not empty, positional parameters are each separated by the first character of the value of the +IFS+ variable. - If the +IFS+ variable exists and has an empty value, positional parameters are just concatenated without any separator. - If the +IFS+ variable does not exist, positional parameters are each separated by a space character. -- + If link:expand.html#split[field-splitting] is applied to an expansion result of this parameter, the value is first split into the original positional parameters and then further split depending on the current +IFS+ variable. The first splitting is performed even if the +IFS+ variable is empty. [[sp-at]]+@+:: This special parameter represents the whole <> like the +*+ special parameter above. The difference between the two is the results of expansion that occurs between a pair of link:syntax.html#quotes[double-quotation marks]. If the +@+ special parameter is expanded inside double-quotations, the result is link:expand.html#split[field-split] into the exact positional parameter values. If there are no positional parameters, the expansion yields no word rather than an empty word. (Even if the expansion is double-quoted, the result is not always a single word.) + -- - When there are no positional parameters, the command words +echo 1 "$@" 2+ are expanded to the three words +echo+, +1+, and +2+. - When positional parameters are the three words +1+, +2 2+, and +3+, the command words +echo "$@"+ are expanded to the four words +echo+, +1+, +2 2+, and +3+, and the words +echo "a$@b"+ to the four words +echo+, +a1+, +2 2+, and +3b+. -- [[variables]] == Variables dfn:[Variables] are parameters the user can assign values to. Each variable has a name that identifies it and a value that defines the results of expansion. A variable name is composed of one or more alphanumeric characters and underscores (+_+). A name cannot start with a digit. Other characters may be used in a name depending on internationalization support of your environment. Variables that are exported to external commands are called dfn:[environment variables]. They are passed to all external commands the shell invokes. Variables passed to the shell in invocation will be automatically exported. You can assign to variables by a link:syntax.html#simple[simple command] as well as the link:_typeset.html[typeset built-in]. You can remove variables by using the link:_unset.html[unset built-in]. [[shellvars]] === Variables used by the shell The following variables are used by the shell for special purposes. [[sv-cdpath]]+CDPATH+:: This variable is used by the cd built-in to find a destination directory. [[sv-columns]]+COLUMNS+:: This variable specifies the width (the number of character columns) of the terminal screen. The value affects the display of link:lineedit.html[line-editing]. [[sv-command_not_found_handler]]+COMMAND_NOT_FOUND_HANDLER+:: When the shell cannot find a command to be executed, the value of this variable is interpreted and executed instead. You can override the shell's error handling behavior with this variable. See link:exec.html#simple[Execution of simple commands] for detail. + This feature is disabled in the link:posix.html[POSIXly-correct mode]. [[sv-dirstack]]+DIRSTACK+:: This array variable is used by the shell to store the directory stack contents. If you modify the value of this variable, the directory stack may be corrupted. [[sv-echo_style]]+ECHO_STYLE+:: This variable specifies the behavior of the link:_echo.html[echo built-in]. [[sv-env]]+ENV+:: When an link:interact.html[interactive] shell is started in the link:posix.html[POSIXly-correct mode], the value of this variable is used to find the initialization file. See link:invoke.html#init[Initialization of yash]. [[sv-fcedit]]+FCEDIT+:: This variable specifies an editor program used to edit command lines during execution of the link:_fc.html[fc built-in]. [[sv-handled]]+HANDLED+:: This variable can be set in the <> to tell the shell not to produce a further error message. See link:exec.html#simple[Execution of simple commands] for detail. [[sv-histfile]]+HISTFILE+:: This variable specifies the pathname of the file to save the link:interact.html#history[command history] in. [[sv-histrmdup]]+HISTRMDUP+:: This variable specifies the number of link:interact.html#history[command history] items to be checked for duplication. When the shell is adding a new history item to the command history, if some of the most recent {{n}} items have the same contents as the new one, then the duplicate existing items are removed from the history before the new one is added, where {{n}} is the value of this variable. + If the value of this variable is +1+, for example, the most recent item is removed when a new item that have the same contents is added. + Items older than the {{n}}th recent item are not removed. No items are removed if the value of this variable is +0+. All items are subject to removal if the variable value is greater than or equal to the value of the <>. [[sv-histsize]]+HISTSIZE+:: This variable specifies the maximum number of items in the link:interact.html#history[command history]. [[sv-home]]+HOME+:: This variable specifies the pathname of the user's home directory and affects results of link:expand.html#tilde[tilde expansion] and link:_cd.html[cd built-in]. [[sv-ifs]]+IFS+:: This variable specifies separators used in link:expand.html#split[field splitting]. The variable value is initialized to the three characters of a space, a tab, and a newline when the shell is started. [[sv-lang]]+LANG+:: [[sv-lc_all]]+LC_ALL+:: [[sv-lc_collate]]+LC_COLLATE+:: [[sv-lc_ctype]]+LC_CTYPE+:: [[sv-lc_messages]]+LC_MESSAGES+:: [[sv-lc_monetary]]+LC_MONETARY+:: [[sv-lc_numeric]]+LC_NUMERIC+:: [[sv-lc_time]]+LC_TIME+:: These variables specify a locale in which the shell runs. The shell chooses the file input/output encoding, the error message language, etc. according to the locale specified. + Unless the shell is link:interact.html[interactive] and not in the link:posix.html[POSIXly-correct mode], the value of the +LC_CTYPE+ variable is considered only when the shell is started. Once the shell has been initialized, changing the value of +LC_CTYPE+ will have no effect on the shell's behavior. [[sv-lineno]]+LINENO+:: The value of this variable is automatically set to the line number in which the currently executed command appears in the file. + In the link:interact.html[interactive shell], the line number is reset to 1 each time the shell reads and executes a command. + If you assign to or remove this variable, it will no longer provide line numbers. [[sv-lines]]+LINES+:: This variable specifies the height (the number of character lines) of the terminal screen. The value affects the display of link:lineedit.html[line-editing]. [[sv-mail]]+MAIL+:: This variable specifies the pathname of a file that is checked in link:interact.html#mailcheck[mail checking]. [[sv-mailcheck]]+MAILCHECK+:: This variable specifies how often the shell should do link:interact.html#mailcheck[mail checking]. The value has to be specified as a positive integer in seconds. The value is initialized to the default value of +600+ when the shell is started. [[sv-mailpath]]+MAILPATH+:: This variable specifies the pathnames of files that are checked in link:interact.html#mailcheck[mail checking]. [[sv-nlspath]]+NLSPATH+:: The POSIX standard prescribes that the value of this variable specifies pathname templates of locale-dependent message data files, but yash does not use it. [[sv-oldpwd]]+OLDPWD+:: This variable is set to the previous working directory path when you change the working directory by using the link:_cd.html[cd] or other built-ins. This variable is exported by default. [[sv-optarg]]+OPTARG+:: When the link:_getopts.html[getopts built-in] parses an option that takes an argument, the argument value is assigned to this variable. [[sv-optind]]+OPTIND+:: The value of this variable specifies the index of an option that is to be parsed by the next link:_getopts.html[getopts built-in] execution. This variable is initialized to +1+ when the shell is started. [[sv-path]]+PATH+:: This variable specifies paths that are searched for a command in link:exec.html#search[command search]. [[sv-ppid]]+PPID+:: The value of this variable is the process ID of the shell's parent process, which is a positive integer. This variable is initialized when the shell is started. The value is not changed when the shell makes a new link:exec.html#subshell[subshell]. [[sv-prompt_command]]+PROMPT_COMMAND+:: The shell interprets and executes the value of this variable before printing each command prompt if the shell is link:interact.html[interactive] and not in the link:posix.html[POSIXly-correct mode]. This behavior is equivalent to executing the command ifdef::basebackend-html[] pass:[eval -i -- "${PROMPT_COMMAND-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${PROMPT_COMMAND-}"`] before each command prompt, but its exit status does not affect the expansion of the +?+ special parameter in the next command. [[sv-ps1]]+PS1+:: This variable specifies the main command prompt string printed by an link:interact.html[interactive] shell. See link:interact.html#prompt[Prompts] for the format of the variable value. The value is initialized to either +$ + or +# + depending on whether the effective user ID of the shell process is zero or not. [[sv-ps1r]]+PS1R+:: This variable specifies the auxiliary prompt string printed to the right of the cursor when you input a command line to an link:interact.html[interactive] shell. See link:interact.html#prompt[Prompts] for the format of the variable value. [[sv-ps1s]]+PS1S+:: This variable specifies the font style of command strings you enter to an link:interact.html[interactive] shell. See link:interact.html#prompt[Prompts] for the format of the variable value. [[sv-ps2]]+PS2+:: This variable is like the <> variable, but it is used for the second and following lines of a command that is longer than one line. See link:interact.html#prompt[Prompts] for the format of the variable value. The value is initialized to +> + when the shell is started. [[sv-ps2r]]+PS2R+:: This variable is like the <> variable, but it is used when <> is used. See link:interact.html#prompt[Prompts] for the format of the variable value. [[sv-ps2s]]+PS2S+:: This variable is like the <> variable, but it is used when <> is used. See link:interact.html#prompt[Prompts] for the format of the variable value. [[sv-ps4]]+PS4+:: The value of this variable is printed before each command trace output when the link:_set.html#so-xtrace[xtrace option] is enabled. The value is subject to link:expand.html#params[parameter expansion], link:expand.html#cmdsub[command substitution], link:expand.html#arith[arithmetic expansion]. You can also use backslash notations if the shell is not in the link:posix.html[POSIXly-correct mode]. The value is initialized to ++ + when the shell is started. [[sv-ps4s]]+PS4S+:: This variable is like the <> variable, but it is used when <> is used. You can use this variable to modify font style of command trace output. [[sv-pwd]]+PWD+:: The value of this variable is the pathname of the current working directory. The value is set when the shell is started and reset each time the working directory is changed by the link:_cd.html[cd] or other built-ins. This variable is exported by default. [[sv-random]]+RANDOM+:: You can use this variable to get random numbers. The value of this variable is a uniformly distributed random integer between 0 and 32767 (inclusive). You will get a different number each time the variable is expanded. + You can set the ``seed'' of random numbers by assigning a non-negative integer to the variable. + If you remove this variable, it will no longer work as a random number generator. If the shell was invoked in the link:posix.html[POSIXly-correct mode], this variable does not work as a random number generator. [[sv-term]]+TERM+:: This variable specifies the type of the terminal in which the shell is running. The value affects the behavior of link:lineedit.html[line-editing]. This variable has to be exported to take effect. [[sv-yash_after_cd]]+YASH_AFTER_CD+:: The shell interprets and executes the value of this variable after each time the shell's working directory is changed by the link:_cd.html[cd] or other built-ins. This behavior is equivalent to executing the command ifdef::basebackend-html[] pass:[eval -i -- "${YASH_AFTER_CD-}"] endif::basebackend-html[] ifndef::basebackend-html[`eval -i -- "${YASH_AFTER_CD-}"`] after the directory was changed. [[sv-yash_loadpath]]+YASH_LOADPATH+:: This variable specifies directories the dot built-in searches for a script file. More than one directory can be specified by separating them by colons like the <> variable. When the shell is started, this variable is initialized to the pathname of the directory where common script files are installed. [[sv-yash_le_timeout]]+YASH_LE_TIMEOUT+:: This variable specifies how long the shell should wait for a next possible input from the terminal when it encountered an ambiguous control sequence while link:lineedit.html[line-editing]. The value must be specified in milliseconds. If you do not define this variable, the default value of 100 milliseconds is assumed. [[sv-yash_ps1]]+YASH_PS1+:: [[sv-yash_ps1r]]+YASH_PS1R+:: [[sv-yash_ps1s]]+YASH_PS1S+:: [[sv-yash_ps2]]+YASH_PS2+:: [[sv-yash_ps2r]]+YASH_PS2R+:: [[sv-yash_ps2s]]+YASH_PS2S+:: [[sv-yash_ps4]]+YASH_PS4+:: [[sv-yash_ps4s]]+YASH_PS4S+:: When not in the link:posix.html[POSIXly-correct mode], if any of these variables is defined, it takes precedence over the corresponding variable without the +YASH_+ prefix in the name (e.g. +PS1+). These variables are ignored in the POSIXly-correct mode. You should define them to include yash-specific notations in the link:interact.html#prompt[prompt], so that unhandled notations do not mangle the prompt in the POSIXly-correct mode. [[sv-yash_version]]+YASH_VERSION+:: The value is initialized to the version number of the shell when the shell is started. [[arrays]] === Arrays An dfn:[array] is a variable that contains zero or more strings. The string values of an array are identified by natural numbers (like <>). You can assign values to an array by using a link:syntax.html#simple[simple command] as well as the link:_array.html[array built-in]. You can use the link:_unset.html[unset built-in] to remove arrays. Arrays cannot be exported as arrays. When an array is exported, it is treated as a normal variable whose value is a concatenation of all the array values, each separated by a colon. Arrays are not supported in the link:posix.html[POSIXly-correct mode]. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/pattern.txt000066400000000000000000000147411354143602500154110ustar00rootroot00000000000000= Pattern matching notation :encoding: UTF-8 :lang: en //:title: Yash manual - Pattern matching notation :description: This page describes pattern matching notation supported by yash. dfn:[Pattern matching notation] is a syntax of dfn:[patterns] that represent particular sets of strings. When a string is included in the set of strings a pattern represents, the pattern is said to dfn:[match] the string. Whether a pattern matches a string or not is defined as follows. [[normal]] == Normal characters A character that is not link:syntax.html#quotes[quoted] or any of special characters defined below is a normal character, which matches the character itself. For example, the pattern +abc+ matches the string +abc+, and not any other strings. [[single]] == Single-character wildcard The character +?+ matches any single character. For example, the pattern +a?c+ matches any three-character strings that starts with +a+ and ends with +c+, such as +aac+, +abc+, and +a;c+. [[multiple]] == Multi-character wildcard The character +*+ matches any strings (of any length, including the empty string). For example, the pattern +a*c+ matches any string that starts with +a+ and ends with +c+, such as +ac+, +abc+, and +a;xyz;c+. [[bracket]] == Bracket expression A pattern that is enclosed by brackets (+[+ and +]+) is a dfn:[bracket expression]. A bracket expression must have at least one character between the brackets. The characters between the brackets are interpreted as a dfn:[bracket expression pattern], which is a below-defined special notation for bracket expression. A bracket expression pattern represents a set of characters. The bracket expression matches any one of the characters in the set the bracket expression pattern represents. If the opening bracket (+[+) is followed by an exclamation mark (+!+), the exclamation is not treated as part of the bracket expression pattern and the whole bracket expression instead matches a character that is _not_ included in the set the bracket expression pattern represents. If the opening bracket is followed by a caret (+^+), it is treated like an exclamation mark as above (but shells other than yash may treat the caret differently). If the opening bracket (or the following exclamation or caret, if any) is followed by a closing bracket (+]+), it is treated as part of the bracket expression pattern rather than the end of the bracket expression. You cannot link:syntax.html#quotes[quote] characters in the bracket expression pattern because quotation is treated before bracket expression. An opening bracket in a pattern is treated as a normal character if it is not the beginning of a valid bracket expression. [[bra-normal]] == Normal characters (in bracket expression pattern) A character that is not any of special characters defined below is a normal character, which represents the character itself. For example, the bracket expression pattern +abc+ represents the set of the three characters +a+, +b+, and +c+. The bracket expression +[abc]+ therefore matches any of the three characters. [[bra-range]] == Range expressions A hyphen preceded and followed by a character (or <>) is a dfn:[range expression], which represents the set of the two characters and all characters between the two in the collation order. A dfn:[collation order] is an order of characters that is defined in the locale data. If a hyphen is followed by a closing bracket (+]+), the bracket is treated as the end of the bracket expression and the hyphen as a normal character. For example, the range expression +3-5+ represents the set of the three characters +3+, +4+, and +5+. The bracket expression +[3-5-]+ therefore matches one of the four characters +3+, +4+, +5+, and +-+. [[bra-colsym]] == Collating symbols A dfn:[collating symbol] allows more than one character to be treated as a single character in matching. A collating symbol is made up of one or more characters enclosed by the special brackets +[.+ and +.]+. One or more characters that are treated as a single character in matching are called a dfn:[collating element]. Precisely, a bracket expression pattern represents a set of collating elements and a bracket expression matches a collating element rather than a character, but we do not differentiate them for brevity here. For example, the character combination ``ch'' was treated as a single character in the traditional Spanish language. If this character combination is registered as a collating element in the locale data, the bracket expression +[[.ch.]df]+ matches one of +ch+, +d+, and +f+. [[bra-eqclass]] == Equivalence classes An dfn:[equivalence class] represents a set of characters that are considered _equivalent_. A equivalence class is made up of a character (or more precisely, a collating element) enclosed by the special brackets +[=+ and +=]+. An equivalence class represents the set of characters that consists of the character enclosed by the brackets and the characters that are in the same primary equivalence class as the enclosed character. The shell consults the locale data for the definition of equivalence classes in the current locale. For example, if the six characters +a+, +à+, +á+, +â+, +ã+, +ä+ are defined to be in the same primary equivalence class, the bracket expressions +[[=a=]]+, +[[=à=]]+, and +[[=á=]]+ match one of the six. [[bra-chclass]] == Character classes A dfn:[character class] represents a predefined set of characters. A character class is made up of a class name enclosed by the special brackets +[:+ and +:]+. The shell consults the locale data for which class a character belongs to. The following character classes can be used in all locales: +[:lower:]+:: set of lowercase letters +[:upper:]+:: set of uppercase letters +[:alpha:]+:: set of letters, including the +[:lower:]+ and +[:upper:]+ classes. +[:digit:]+:: set of decimal digits +[:xdigit:]+:: set of hexadecimal digits +[:alnum:]+:: set of letters and digits, including the +[:alpha:]+ and +[:digit:]+ classes. +[:blank:]+:: set of blank characters, not including the newline character +[:space:]+:: set of space characters, including the newline character +[:punct:]+:: set of punctuations +[:print:]+:: set of printable characters +[:cntrl:]+:: set of control characters For example, the bracket expression `[[:lower:][:upper:]]` matches a lower or upper case character. In addition to the classes listed above, other classes may be used depending on the definition of the current locale. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/posix.txt000066400000000000000000000124321354143602500150710ustar00rootroot00000000000000= POSIXly-correct mode :encoding: UTF-8 :lang: en //:title: Yash manual - POSIXly-correct mode :description: This page describes the behavior of yash's POSIXly-correct mode. Yash behaves as defined in link:http://pubs.opengroup.org/onlinepubs/9699919799/[POSIX.1-2008], Shell & Utilities for the most part, but some functionalities disobey POSIX for usability. When full POSIX-conformance is needed, you can enable the dfn:[POSIXly-correct mode] to make yash obey POSIX as much as possible. If yash is started with the name ``sh'', the POSIXly-correct mode is automatically enabled. The +-o posixly-correct+ command-line option also enables the POSIXly-correct mode. After yash has been started, the POSIXly-correct mode can be enabled by executing the command string +link:_set.html[set] -o posixly-correct+. When the POSIXly-correct mode is on, yash not only tries to obey the requirements by POSIX, but also treats as errors most conditions where the behavior is _undefined_ or _unspecified_ by POSIX. As a result, most yash-specific functionalities are disabled in the POSIXly-correct mode. Below is the complete list of the behavioral differences between when yash is in the POSIXly-correct mode and when not. When the POSIXly-correct mode is enabled: - Different link:invoke.html#init[initialization scripts] are used. - If the shell was started with the link:invoke.html#arguments[+-c+ option], +sh -c+ (instead of +yash -c+) is printed as the script pathname on a syntax error. - Global link:syntax.html#aliases[aliases] are not substituted. - Nested commands in a link:syntax.html#compound[compound command] must not be empty. - The link:syntax.html#for[for loop] iteration variable is created as global, regardless of the link:_set.html#so-forlocal[for-local] shell option. The variable must have a portable (ASCII-only) name. - The first pattern in a link:syntax.html#case[case command] cannot be +esac+. - The +!+ keyword cannot be followed by +(+ without any whitespaces in-between. - The link:syntax.html#double-bracket[double-bracket command] cannot be used. - The +function+ keyword cannot be used for link:syntax.html#funcdef[function definition]. The function must have a portable (ASCII-only) name. - link:syntax.html#simple[Simple commands] cannot assign to link:params.html#arrays[arrays]. - Changing the value of the link:params.html#sv-lc_ctype[+LC_CTYPE+ variable] after the shell has been initialized does not affect the shell's locale. - The link:params.html#sv-random[+RANDOM+ variable] cannot be used to generate random numbers. - link:expand.html#tilde[Tilde expansion] only expands +~+ and +~{{username}}+. - link:expand.html#params[Parameter expansion] cannot be nested. No link:expand.html#param-index[indexes] or link:expand.html#param-mod[modifiers] with {{word2}} are allowed. - The commands in a link:expand.html#cmdsub[command substitution] of the form +$({{commands}})+ are parsed every time the substitution is executed. - In link:expand.html#arith[arithmetic expansion], fractional numbers and the `++` and `--` operators cannot be used. All variables must be numeric. - The operand of a link:redir.html[redirection] cannot be the integer prefix to a next redirection operator. - A link:syntax.html#compound[compound command] with a link:redir.html[redirection] cannot be immediately followed by a keyword like +}+ and +fi+. - In a link:redir.html#file[redirection to a file], if the link:expand.html#glob[pathname expansion] yielded more than one or no pathname, it is not immediately treated as an error. Instead, the shell tries to treat the word before the expansion as a pathname. - A file descriptor must be readable and writable when duplicated by the +<&+ and +>&+ link:redir.html#dup[redirection] operator, respectively. - link:redir.html#socket[Socket redirection], link:redir.html#here[here strings], link:redir.html#pipe[pipe redirection], and link:redir.html#process[process redirection] cannot be used. - When link:exec.html#simple[executing a simple command], failure in command search does not trigger execution of the link:params.html#sv-command_not_found_handler[+COMMAND_NOT_FOUND_HANDLER+ variable]. - In link:exec.html#search[command search], a link:builtin.html#types[regular built-in] needs to have a corresponding external command for the built-in to be found. - Some link:builtin.html[built-ins] behave differently. Especially, long command-line options (as well as some others) cannot be used. - A link:interact.html[non-interactive] shell exits when a link:builtin.html#types[special built-in] is given a syntactically wrong arguments or when an error occurs in assignment or redirection with a special built-in. - An link:interact.html[interactive] shell does not execute the link:params.html#sv-prompt_command[+PROMPT_COMMAND+ variable] before printing a prompt. The values of the link:params.html#sv-ps1[+PS1+], link:params.html#sv-ps2[+PS2+], and link:params.html#sv-ps4[+PS4+] variables are parsed differently. Prompt variables with a +YASH_+ prefix (e.g. link:params.html#sv-yash_ps1[+YASH_PS1+]) are not used. - In link:interact.html#mailcheck[mail checking], a notification message is printed if the file has been modified, regardless of whether the file is empty. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/redir.txt000066400000000000000000000235161354143602500150410ustar00rootroot00000000000000= Redirection :encoding: UTF-8 :lang: en //:title: Yash manual - Redirection :description: This page describes redirections supported by yash. dfn:[Redirection] is a feature you can use to modify file descriptors of commands. By using redirection, you can execute commands with their standard input/output connected with files or devices other than the terminal. You can do redirection by adding redirection operators to a command (link:syntax.html#simple[simple command] or link:syntax.html#compound[compound command]) In a simple command, redirection operators may appear anywhere in the command as long as operator tokens are separated from other tokens. In a compound command, redirection operators must appear at the end of the command. Redirection operators are processed before the command body is executed. More than one redirection operator in a command are processed in the order of appearance. Redirection operators affect only the command in which they appear, except when they appear in an link:_exec.html[exec built-in] without command operands. That is, file descriptors modified by redirection are restored after the command has finished. A redirection operator starts with +<+ or +>+. Redirection operators starting with +<+ affects the standard input (file descriptor 0) by default. Redirection operators starting with +>+ affects the standard output (file descriptor 1) by default. To affect another file descriptor, you can prefix a redirection operator with a non-negative integer; the operator will affect the file descriptor specified by the integer. The integer must immediately precede the +<+ or +>+ without any whitespaces in between. The integer must not be link:syntax.html#quotes[quoted], either. [[file]] == Redirection to files The most common type of redirection is redirection to files. Redirection of input:: +< {{token}}+ Redirection of output:: +> {{token}}+ + +>| {{token}}+ + +>> {{token}}+ Redirection of input and output:: +<> {{token}}+ The {{token}} is subject to the link:expand.html[four expansions]. It is also subject to link:expand.html#glob[pathname expansion] if the shell is link:interact.html[interactive]. The expansion result is treated as the pathname of the file to which redirection is performed. If the pathname expansion does not result in a single pathname, it is an error. In redirection of input, the standard input is replaced with a file descriptor which is open for read-only access to the target file. If the target file cannot be opened for read-only access, it is an error. In redirection of output, the standard output is replaced with a file descriptor which is open for write-only access to the target file. If the target file cannot be opened for write-only access, it is an error. If the target file does not exist, a new empty file is created and opened. If the target file already exists, the file is opened as follows: - For the +>|+ operator, the file is emptied when opened if it is a regular file. - For the +>+ operator, the behavior is the same as the +>|+ operator if the link:_set.html#so-clobber[clobber option] is enabled. If the option is disabled and the file is a regular file, it is treated as an error. - For the +>>+ operator, the file is opened for appending; any output to the file descriptor is appended to the end of the file. In redirection of input and output, the standard input is replaced with a file descriptor which is open for read-and-write access to the target file. If the file does not exist, a new empty file is created and opened. [[socket]] === Socket redirection If the pathname of the target file is of the form +/dev/tcp/{{host}}/{{port}}+ or +/dev/udp/{{host}}/{{port}}+ and the file cannot be opened in the usual manner, a new socket is opened for communication with the {{port}} of the {{host}}. The redirection replaces the standard input or output with the file descriptor to the socket. A stream socket is opened for the form +/dev/tcp/{{host}}/{{port}}+ and a datagram socket for the form +/dev/udp/{{host}}/{{port}}+. The protocol actually used for communication is determined by the socket library the shell uses. Typically, stream sockets use TCP and datagram sockets UDP. In socket redirection, the file descriptor is both readable and writable regardless of the type of the redirection operator used. Socket redirection is yash's extension that is not defined in POSIX. Bash as well has socket redirection as extension. [[dup]] == Duplication of file descriptors Redirection allows duplicating or closing existing file descriptors. Duplication of file descriptor:: +<& {{token}}+ + +>& {{token}}+ The {{token}} is subject to expansion as in <>, but it is treated as a file descriptor rather than a pathname. Thus the expanded {{token}} must be a non-negative integer. The +<&+ and +>&+ operators duplicate the file descriptor specified by {{token}} to the standard input and output, respectively. (The operators can be prefixed with a non-negative integer so that the file descriptor is duplicated to a file descriptor other than the standard input or output.) If the expanded {{token}} is a single hyphen rather than a non-negative integer, the file descriptor is closed rather than duplicated. By default, the +<&+ and +>&+ operators close the standard input and output, respectively, but the operators can be prefixed with a non-negative integer so that another file descriptor is closed. In the link:posix.html[POSIXly-correct mode], a file descriptor must be readable when duplicated by the +<&+ operator and writable when duplicated by the +>&+ operator. [[here]] == Here documents and here strings dfn:[Here document] and dfn:[here string] allow redirection to file descriptors that reads strings directly specified in shell commands. Here document:: +<< {{token}}+ + +<<- {{token}}+ Here string:: +<<< {{token}}+ In a here document or here string, the standard input is replaced with a readable file descriptor. When the command reads from the file descriptor, it will read the contents of the here document/string, which is defined below. When a here document operator (+<<+ or +<<-+) appears in a command, the shell reads the contents of the here document starting from the next line. The contents of here documents are not parsed nor executed as commands. The {{token}} after the operand specifies a delimiter that indicates the end of the contents. (The {{token}} is not subject to any link:expand.html[expansion], but link:syntax.html#quotes[quotation] is processed.) The contents of the here document is terminated just before the first line containing the {{token}} only. When using the +<<-+ operator, all tab characters at the beginning of each line in the here document contents are removed and the delimiter {{token}} may be preceded by tab characters. If there are more than one here document operator on one line, the contents of the here documents are parsed in order: The contents of the first here document starts from the next line and ends before the first line containing the {{token}} that followed the first operator. Just after that line, the contents of the second here document starts, and so on. The contents of here documents are treated literally: whitespaces, tabs, etc. remain as is. The exception is that, when the {{token}} is not link:syntax.html#quotes[quoted] at all: - the contents are subject to link:expand.html#params[parameter expansion], link:expand.html#cmdsub[command substitution], link:expand.html#arith[arithmetic expansion]. - a backslash in the contents is treated as link:syntax.html#quotes[quotation] if and only if it precedes +$+, +`+, +"+, or another backslash. - a backslash followed by a newline is treated as link:syntax.html#quotes[line continuation]. In here string, the {{token}} after the operator is subject to expansion as in <>. The expansion result becomes the contents of the here string. A newline character is automatically appended to the end of here string contents. Here string is yash's extension that is not defined in POSIX. Other shells like bash, ksh, and zsh have the same feature. [[pipe]] == Pipeline redirection dfn:[Pipeline redirection] allows opening pipelines that can be used for arbitrary purposes. Pipeline redirection:: +>>| {{token}}+ The {{token}} is subject to expansion as in <>, but it is treated as a file descriptor rather than a pathname. Thus the expanded {{token}} must be a non-negative integer. Pipeline redirection opens a new pipeline. The standard output (or the file descriptor specified before the operator, if any) is replaced with the file descriptor open for writing to the pipeline. The file descriptor specified by {{token}} is replaced with the file descriptor open for reading from the pipeline. Pipeline redirection is yash's extension that is not defined in POSIX. [[process]] == Process redirection dfn:[Process redirection] creates a pipeline connected to another command. Process redirection:: +<({{command}}...)+ + +>({{command}}...)+ In process redirection, the {{command}} specified is executed in a link:exec.html#subshell[subshell]. If the process redirection is of the form +<({{command}}...)+, the standard output of {{command}} is connected with a pipeline to the standard input of the command the redirection is associated with. If the process redirection is of the form +>({{command}}...)+, the standard input of {{command}} is connected with a pipeline to the standard output of the command the redirection is associated with. Process redirection is yash's extension that is not defined in POSIX. Bash and zsh have a feature called process substitution, which uses the same syntax as yash's process redirection, but incompatibly differs in behavior. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/syntax.txt000066400000000000000000000532601354143602500152610ustar00rootroot00000000000000= Syntax :encoding: UTF-8 :lang: en //:title: Yash manual - Syntax :description: This page defines yash's command syntax and semantics. The shell reads, parses, and executes command line by line. If there is more than one command on a line, all the commands are parsed before executed. If a command is continued to next lines, the shell reads more enough lines to complete the command. On a syntax error, the shell neither reads nor executes any more commands. [[tokens]] == Tokens and keywords A command is composed of one or more tokens. In the shell syntax, a dfn:[token] is a word that is part of a command. Normally, tokens are separated by whitespaces, that is, the space or tab character. Whitespaces inside a command substitution or a parameter expansion, however, do not separate tokens. The following symbols have special meanings in the shell syntax and in most cases separate tokens: ; & | < > ( ) [newline] The following symbols do not separate tokens, but have syntactic meanings: $ ` \ " ' * ? [ # ~ = % The following tokens are treated as dfn:[keywords] depending on the context in which they appear: ! { } [[ case do done elif else esac fi for function if in then until while A token is treated as a keyword when: * it is the first token of a command, * it follows another keyword (except +case+, +for+, and +in+), or * it is a non-first token of a command and is supposed to be a keyword to compose a composite command. If a token begins with `#`, then the `#` and any following characters up to the end of the line are treated as a dfn:[comment], which is completely ignored in syntax parsing. [[quotes]] == Quotations If you want whitespaces, separator characters, or keywords described above to be treated as a normal characters, you must quote the characters using appropriate quotation marks. Quotation marks are not treated as normal characters unless they are themselves quoted. You can use the following three quotation marks: * A backslash (+\+) quotes a character that immediately follows. + The only exception about a backslash is the case where a backslash is followed by a newline. In this case, the two characters are treated as a dfn:[line continuation] rather than a newline being quoted. The two characters are removed from the input and the two lines surrounding the line continuation are concatenated into a single line. * A pair of single-quotation marks (+'+) quote any characters between them except another single-quotation. Note that newlines can be quoted using single-quotations. * Double-quotation marks (+"+) are like single-quotations, but they have a few exceptions: Parameter expansion, command substitution, and arithmetic expansion are interpreted as usual even between double-quotations. A backslash between double-quotations is treated as a quotation mark only when it is followed by +$+, +`+, +"+, +\+, or a newline; other backslashes are treated as normal characters. [[aliases]] == Aliases Tokens that compose a command are subject to dfn:[alias substitution]. A token that matches the name of an alias that has already been defined is substituted with the value of the alias before the command is parsed. Tokens that contain quotations are not alias-substituted since an alias name cannot contain quotation marks. Keywords and command separator characters are not alias-substituted either. There are two kinds of aliases: normal aliases and global aliases. A dfn:[normal alias] can only substitute the first token of a command while a dfn:[global alias] can substitute any part of a command. Global aliases are yash extension that is not defined in POSIX. If a token is alias-substituted with the value of a normal alias that ends with a whitespace, the next token is exceptionally subject to alias substitution for normal aliases. The results of alias substitution are again subject to alias substitution for other aliases (but not for the aliases that have been already applied). You can define aliases using the link:_alias.html[alias built-in] and remove using the link:_unalias.html[unalias built-in]. [[simple]] == Simple commands A command that does not start with a keyword token is a dfn:[simple command]. Simple commands are executed as defined in link:exec.html#simple[Execution of simple commands]. If the first and any number of following tokens of a simple command have the form +{{name}}={{value}}+, they are interpreted as link:params.html#variables[variable] assignments. A variable name must consist of one or more alphabets, digits and/or underlines (+_+) and must not start with a digit. The first token that is not a variable assignment is considered as a command name and all the following tokens (whether or not they have the form +{{name}}={{value}}+) as command arguments. A variable assignment of the form +{{var}}=({{tokens}})+ is interpreted as assignment to an link:params.html#arrays[array]. You can write any number of tokens between a pair of parentheses. Tokens can be separated by not only spaces and tabs but also newlines. [[pipelines]] == Pipelines A dfn:[pipeline] is a sequence of one or more <>, <>, and/or <> that are separated by +|+. A pipeline that has more than one subcommand is executed by executing each subcommand of the pipeline in a subshell simultaneously. The standard output of each subcommand except the last one is redirected to the standard input of the next subcommand. The standard input of the first subcommand and the standard output of the last subcommand are not redirected. The exit status of the pipeline is that of the last subcommand unless the link:_set.html#so-pipefail[pipe-fail option] is enabled, in which case the exit status of the pipeline is that of the last subcommand that exits with a non-zero exit status. If all the subcommands exit with an exit status of zero, the exit status of the pipeline is also zero. A pipeline can be prefixed by +!+, in which case the exit status of the pipeline is _reversed_: the exit status of the pipeline is 1 if that of the last subcommand is 0, and 0 otherwise. Korn shell treats a word of the form +!(...)+ as an extended pathname expansion pattern that is not defined in POSIX. In the link:posix.html[POSIXly-correct mode], the tokens +!+ and +(+ must be separated by one or more white spaces. [NOTE] When the execution of a pipeline finishes, at least the execution of the last subcommand has finished since the exit status of the last subcommand defines that of the whole pipeline. The execution of other subcommands, however, may not have finished then. On the other hand, the execution of the pipeline may not finish soon after that of the last subcommand finished because the shell may choose to wait for the execution of other subcommands to finish. [NOTE] The POSIX standard allows executing any of subcommands in the current shell rather than subshells, though yash does not do so. [[and-or]] == And/or lists An dfn:[and/or list] is a sequence of one or more <> separated by +&&+ or +||+. An and/or list is executed by executing some of the pipelines conditionally. The first pipeline is always executed. The other pipelines are either executed or not executed according to the exit status of the previous pipelines. - If two pipelines are separated by +&&+ and the exit status of the first pipeline is zero, the second pipeline is executed. - If two pipelines are separated by +||+ and the exit status of the first pipeline is not zero, the second pipeline is executed. - In other cases, the execution of the and/or list ends: the second and any remaining pipelines are not executed. The exit status of an and/or list is that of the last pipeline that was executed. Normally, an and/or list must be terminated by a semicolon, ampersand, or newline. See <>. [[async]] == Command separators and asynchronous commands The whole input to the shell must be composed of any number of <> separated by a semicolon or ampersand. A terminating semicolon can be omitted if it is followed by +;;+, +)+, or a newline. Otherwise, an and/or list must be terminated by a semicolon or ampersand. If an and/or list is terminated by a semicolon, it is executed synchronously: the shell waits for the and/or list to finish before executing the next and/or list. If an and/or list is terminated by an ampersand, it is executed asynchronously: after the execution of the and/or list is started, the next and/or list is executed immediately. An asynchronous and/or list is always executed in a link:exec.html#subshell[subshell] and its exit status is zero. If the shell is not doing link:job.html[job control], the standard input of an asynchronous and/or list is automatically redirected to /dev/null. Signal handlers of the and/or list for the SIGINT and SIGQUIT signals are set to ``ignore'' the signal so that the execution of the and/or list cannot be stopped by those signals. When the execution of an asynchronous and/or list is started, the shell remembers its process ID. You can obtain the ID by referencing the link:params.html#sp-exclamation[+!+ special parameter]. You can obtain the current and exit status of the asynchronous list as well by using the link:_jobs.html[jobs] and link:_wait.html[wait] built-ins. [[compound]] == Compound commands Compound commands provide you with programmatic control of shell command execution. [[grouping]] === Grouping A grouping is a list of commands that is treated as a <>. Normal grouping syntax:: +{ {{command}}...; }+ Subshell grouping syntax:: +({{command}}...)+ The +{+ and +}+ tokens are keywords, which must be separated from other tokens. The +(+ and +)+ tokens, however, are special separators that need not to be separated. In the normal grouping syntax, the commands in a grouping are executed in the current shell. In the subshell grouping syntax, the commands are executed in a new link:exec.html#subshell[subshell]. In the link:posix.html[POSIXly-correct mode], a grouping must contain at least one command. If the shell is not in the POSIXly-correct mode, a grouping may contain no commands. The exit status of a grouping is that of the last command in the grouping. If the grouping contains no commands, its exit status is that of the last executed command before the grouping. [[if]] === If command The if command performs a conditional branch. Basic if command syntax:: +if {{condition}}...; then {{body}}...; fi+ Syntax with the else clause:: +if {{condition}}...; then {{body}}...; else {{body}}...; fi+ Syntax with the elif clause:: +if {{condition}}...; then {{body}}...; elif {{condition}}...; then {{body}}...; fi+ Syntax with the elif clause:: +if {{condition}}...; then {{body}}...; elif {{condition}}...; then {{body}}...; else {{body}}...; fi+ For all the syntaxes, the execution of an if command starts with the execution of the {{condition}} commands that follows the +if+ token. If the exit status of the condition commands is zero, the condition is considered as ``true''. In this case, the {{body}} commands that follows the +then+ token are executed and the execution of the if command finishes. If the exit status of the condition commands is non-zero, the condition is considered as ``false''. In this case, the {{condition}} commands for the next elif clause are executed and the exit status is tested in the same manner as above. If there is no elif clause, the {{body}} commands that follow the +else+ token are executed and the execution of the if command finishes. If there is no else clause either, the execution of the if command just ends. An if command may have more than one elif-then clause. The exit status of an if command is that of the {{body}} commands that were executed. The exit status is zero if no {{body}} commands were executed, that is, all the conditions were false and there was no else clause. [[while-until]] === While and until loops The while loop and until loop are simple loops with condition. While loop syntax:: +while {{condition}}...; do {{body}}...; done+ Until loop syntax:: +until {{condition}}...; do {{body}}...; done+ If the shell is not in the link:posix.html[POSIXly-correct mode], you can omit the {{condition}} and/or {{body}} commands of a while/until loop. The execution of a while loop is started by executing the {{condition}} commands. If the exit status of the {{condition}} commands is zero, the shell executes the {{body}} commands and returns to the execution of the {{condition}} commands. The {{condition}} and {{body}} commands are repeatedly executed until the exit status of the {{condition}} commands is non-zero. [NOTE] The {{body}} commands are not executed at all if the first execution of the {{condition}} commands yields a non-zero exit status. An until loop is executed in the same manner as a while loop except that the condition to repeat the loop is reversed: the {{body}} commands are executed when the exit status of the {{condition}} commands is non-zero. The exit status of a while/until loop is that of the last executed {{body}} command. The exit status is zero if the {{body}} commands are empty or were not executed at all. [[for]] === For loop The for loop repeats commands with a variable assigned one of given values in each round. For loop syntax:: +for {{varname}} in {{word}}...; do {{command}}...; done+ + +for {{varname}} do {{command}}...; done+ The {{word}} list after the +in+ token may be empty, but the semicolon (or newline) before the +do+ token is required even in that case. The {{word}}s are not treated as keywords, but you need to <> separator characters (such as +&+ and +|+) to include them as part of a {{word}}. The {{command}} list may be empty if not in the link:posix.html[POSIXly-correct mode]. The {{varname}} must be a portable (ASCII-only) name in the POSIXly-correct mode. The execution of a for loop is started by expanding the {{word}}s in the same manner as in the execution of a <>. If the +in+ and {{word}} tokens are omitted, the shell assumes the {{word}} tokens to be +"$@"+. Next, the following steps are taken for each word expanded (in the order the words were expanded): . Assign the word to the variable whose name is {{varname}}. . Execute the {{command}}s. By default, if a for loop is executed within a function, {{varname}} is created as a link:exec.html#localvar[local variable], even if it already exists globally. Turning off the link:_set.html#so-forlocal[for-local] shell option or enabling the link:posix.html[POSIXly-correct mode] mode will disable this behavior. If the expansion of the {{word}}s yields no words, no variable is created and the {{command}}s are not executed at all. The exit status of a for loop is that of the last executed {{command}}. The exit status is zero if the {{command}}s are not empty and not executed at all. If the {{command}}s are empty, the exit status is that of the last executed command before the for loop. If the variable is read-only, the execution of the for loop is interrupted and the exit status will be non-zero. [[case]] === Case command The case command performs a pattern matching to select commands to execute. Case command syntax:: +case {{word}} in {{caseitem}}... esac+ Case item syntax:: +({{patterns}}) {{command}}...;;+ The {{word}} between the +case+ and +in+ tokens must be exactly one word. The {{word}} is not treated as a keyword, but you need to <> separator characters (such as +&+ and +|+) to include them as part of the {{word}}. Between the +in+ and +esac+ tokens you can put any number of case items (may be none). You can omit the first +(+ token of a case item and the last +;;+ token before the +esac+ token. If the last {{command}} of a case item is terminated by a semicolon, you can omit the semicolon as well. The {{command}}s in a case item may be empty. The {{patterns}} in a case item are one or more tokens each separated by a +|+ token. The execution of a case command starts with subjecting the {{word}} to link:expand.html[the four expansions]. Next, the following steps are taken for each case item (in the order of appearance): . For each word in the {{patterns}}, expand the word in the same manner as the {{word}} and test if the expanded pattern link:pattern.html[matches] the expanded word. (If a pattern is found that matches the word, the remaining patterns are not expanded nor tested, so some of the {{patterns}} may not be expanded. Yash expands and tests the patterns in the order of appearance, but it may not be the case for other shells.) . If one of the {{patterns}} was found to match the {{word}} in the previous step, the {{command}}s in this case item are executed and the execution of the whole case item ends. Otherwise, proceed to the next case item. The exit status of a case command is that of the {{command}}s executed. The exit status is zero if no {{command}}s were executed, that is, there were no case items, no matching pattern was found, or no commands were associated with the matching pattern. In the link:posix.html[POSIXly-correct mode], the first pattern in a case item cannot be +esac+ (even if you do not omit the +(+ token). [[double-bracket]] === Double-bracket command The dfn:[double-bracket command] is a syntactic construct that works similarly to the link:_test.html[test built-in]. It expands and evaluates the words between the brackets. Double-bracket command syntax:: +[[ {{expression}} ]]+ The {{expression}} can be a single primary or combination of primaries and operators. The expression syntax is parsed when the command is parsed, not executed. Operators (either primary or non-primary) must not be link:#quotes[quoted], or it will be parsed as a normal word. When the command is executed, operand words are subjected to the link:expand.html[four expansions], but not brace expansion, field splitting, or pathname expansion. In the double-bracket command, the following primaries from the test built-in can be used: Unary primaries:: +-b+, +-c+, +-d+, +-e+, +-f+, +-G+, +-g+, +-h+, +-k+, +-L+, +-N+, +-n+, +-O+, +-o+, +-p+, +-r+, +-S+, +-s+, +-t+, +-u+, +-w+, +-x+, +-z+ Binary primaries:: +-ef+, +-eq+, +-ge+, +-gt+, +-le+, +-lt+, +-ne+, +-nt+, +-ot+, +-veq+, +-vge+, +-vgt+, +-vle+, +-vlt+, +-vne+, +===+, +!==+, +=~+, +<+, +>+ Additionally, some binary primaries can be used to compare strings, which works slightly differently from those for the test built-in: The +=+ primary treats the right-hand-side operand word as a link:pattern.html[pattern] and tests if it matches the left-hand-side operand word. The +==+ primary is the same as +=+. The +!=+ primary is negation of the +=+ primary (reverse result). The operand word of a primary must be quoted if it is +]]+ or can be confused with another primary operator. [NOTE] More primaries may be added in future versions of the shell. You should quote any words that start with a hyphen. [NOTE] The `<=` and `>=` binary primaries cannot be used in the double-bracket command because it cannot be parsed correctly in the shell grammar. The following operands (listed in the descending order of precedence) can be used to combine primaries: +( {{expression}} )+:: A pair of parentheses change operator precedence. +! {{expression}}+:: An exclamation mark negates (reverses) the result. +{{expression}} && {{expression}}+:: A double ampersand represents logical conjugation (the ``and'' operation). The entire expression is true if and only if the operand expressions are both true. The left-hand-side expression is first expanded and tested. The right-hand-side is expanded only if the left-hand-side is true. +{{expression}} || {{expression}}+:: A double vertical line represents logical conjugation (the ``or'' operation). The entire expression is false if and only if the operand expressions are both false. The left-hand-side expression is first expanded and tested. The right-hand-side is expanded only if the left-hand-side is false. [NOTE] Unlike the test built-in, neither +-a+ nor +-o+ can be used as a binary operator in the double-bracket command. The exit status of the double-bracket command is 0 if {{expression}} is true, 1 if false, and 2 if it cannot be evaluated because of expansion error or any other reasons. [NOTE] The double-bracket command is also supported in bash, ksh, mksh, and zsh, but not defined in the POSIX standard. The behavior slightly differs between the shells. The test built-in should be preferred over the double-bracket command for maximum portability. [[funcdef]] == Function definition The function definition command defines a link:exec.html#function[function]. Function definition syntax:: +{{funcname}} ( ) {{compound_command}}+ + +function {{funcname}} {{compound_command}}+ + +function {{funcname}} ( ) {{compound_command}}+ In the first syntax without the +function+ keyword, {{funcname}} cannot contain any special characters such as semicolons and quotation marks. In the second and third syntax, which cannot be used in the link:posix.html[POSIXly-correct mode], {{funcname}} is subjected to link:expand.html[the four expansions] when executed. In the POSIXly-correct mode, {{funcname}} is limited to a portable (ASCII-only) name. When a function definition command is executed, a function whose name is {{funcname}} is defined with its body being {{compound_command}}. A function definition command cannot be directly link:redir.html[redirected]. Any redirections that follow a function definition are associated with {{compound_command}} rather than the whole function definition command. In +func() { cat; } >/dev/null+, for example, it is not +func() { cat; }+ but +{ cat; }+ that is redirected. The exit status of a function definition is zero if the function was defined without errors, and non-zero otherwise. // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/doc/yash.txt.in000066400000000000000000000005171354143602500153010ustar00rootroot00000000000000= YASH(1) Yuki Watanabe v{yashversion}, :encoding: UTF-8 :lang: en == Name yash - a POSIX-compliant command line shell == Synopsis +yash [options...] [--] [operands...]+ :leveloffset: 1 // (Replaced with generated contents list) // :leveloffset: 0 // vim: set filetype=asciidoc textwidth=78 expandtab: yash-2.49/exec.c000066400000000000000000002063341354143602500135170ustar00rootroot00000000000000/* Yash: yet another shell */ /* exec.c: command execution */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "exec.h" #include #include #include #if HAVE_GETTEXT # include #endif #include #include #if HAVE_PATHS_H # include #endif #include #include #include #include #include #include #include #include #include #include "alias.h" #include "builtin.h" #include "expand.h" #if YASH_ENABLE_HISTORY # include "history.h" #endif #include "input.h" #include "job.h" #include "option.h" #include "parser.h" #include "path.h" #include "plist.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "xfnmatch.h" #include "yash.h" #if YASH_ENABLE_DOUBLE_BRACKET # include "builtins/test.h" #endif #if YASH_ENABLE_LINEEDIT # include "lineedit/complete.h" # include "lineedit/lineedit.h" #endif /* type of command execution */ typedef enum { E_NORMAL, /* normal execution */ E_ASYNC, /* asynchronous execution */ E_SELF, /* execution in the shell's own process */ } exec_T; /* info about file descriptors of pipes */ typedef struct pipeinfo_T { int pi_fromprevfd; /* reading end of the pipe from the previous process */ int pi_tonextfds[2]; /* both ends of the pipe to the next process */ /* -1 is assigned to unused members. */ } pipeinfo_T; #define PIPEINFO_INIT { -1, { -1, -1 }, } /* values used to specify the behavior of command search. */ typedef enum srchcmdtype_T { SCT_EXTERNAL = 1 << 0, /* search for an external command */ SCT_BUILTIN = 1 << 1, /* search for a built-in */ SCT_FUNCTION = 1 << 2, /* search for a function */ SCT_ALL = 1 << 3, /* search all */ SCT_STDPATH = 1 << 4, /* search the standard PATH */ SCT_CHECK = 1 << 5, /* check command existence */ } srchcmdtype_T; typedef enum cmdtype_T { CT_NONE, CT_EXTERNALPROGRAM, CT_SPECIALBUILTIN, CT_SEMISPECIALBUILTIN, CT_REGULARBUILTIN, CT_FUNCTION, } cmdtype_T; /* info about a simple command to execute */ typedef struct commandinfo_T { cmdtype_T type; /* type of command */ union { const char *path; /* command path (for external program) */ main_T *builtin; /* body of built-in */ command_T *function; /* body of function */ } value; } commandinfo_T; #define ci_path value.path #define ci_builtin value.builtin #define ci_function value.function typedef enum exception_T { E_NONE, E_CONTINUE, E_RETURN, E_BREAK_ITERATION, E_CONTINUE_ITERATION, } exception_T; /* state of currently executed loop */ typedef struct execstate_T { unsigned loopnest; /* level of nested loops */ unsigned breakloopnest; /* target level of "break" */ bool noreturn; /* true when the "return" built-in is not allowed */ bool iterating; /* true when iterative execution is ongoing */ } execstate_T; static void exec_pipelines(const pipeline_T *p, bool finally_exit); static void exec_pipelines_async(const pipeline_T *p) __attribute__((nonnull)); static void exec_if(const command_T *c, bool finally_exit) __attribute__((nonnull)); static inline bool exec_condition(const and_or_T *c); static void exec_for(const command_T *c, bool finally_exit) __attribute__((nonnull)); static void exec_while(const command_T *c, bool finally_exit) __attribute__((nonnull)); static void exec_case(const command_T *c, bool finally_exit) __attribute__((nonnull)); static void exec_funcdef(const command_T *c, bool finally_exit) __attribute__((nonnull)); static void exec_commands(command_T *c, exec_T type); static bool is_errexit_condition(void) __attribute__((pure)); static bool is_errreturn_condition(void) __attribute__((pure)); static void apply_errexit_errreturn(const command_T *c); static bool is_err_condition_for(const command_T *c) __attribute__((pure)); static inline void next_pipe(pipeinfo_T *pi, bool next) __attribute__((nonnull)); static pid_t exec_process( command_T *restrict c, exec_T type, pipeinfo_T *restrict pi, pid_t pgid) __attribute__((nonnull)); static inline void connect_pipes(pipeinfo_T *pi) __attribute__((nonnull)); static void become_child(bool leave); static void search_command( const char *restrict name, const wchar_t *restrict wname, commandinfo_T *restrict ci, enum srchcmdtype_T type) __attribute__((nonnull)); static inline bool is_special_builtin(const char *cmdname) __attribute__((nonnull,pure)); static bool command_not_found_handler(void *const *argv) __attribute__((nonnull)); static void exec_nonsimple_command(command_T *c, bool finally_exit) __attribute__((nonnull)); static void exec_simple_command(const commandinfo_T *ci, int argc, char *argv0, void **argv, bool finally_exit) __attribute__((nonnull)); static void exec_external_program( const char *path, int argc, char *argv0, void **argv, char **envs) __attribute__((nonnull)); static void print_xtrace(void *const *argv); static void exec_fall_back_on_sh( int argc, char *const *argv, char *const *env, const char *path) __attribute__((nonnull(2,3,4))); static void exec_function_body( command_T *body, void *const *args, bool finally_exit, bool complete) __attribute__((nonnull)); static inline int xexecve( const char *path, char *const *argv, char *const *envp) __attribute__((nonnull(1))); static int exec_iteration(void *const *commands, const char *codename) __attribute__((nonnull)); /* exit status of the last command */ int laststatus = Exit_SUCCESS; /* exit status of the command preceding the currently executed trap action */ int savelaststatus = -1; // -1 if not in a trap handler /* exit status of the last command substitution */ static int lastcmdsubstatus; /* exit status of the command that immediately preceded the EXIT trap. */ int exitstatus = -1; // -1 if not executing the EXIT trap /* the process ID of the last asynchronous list */ pid_t lastasyncpid; /* This flag is set to true while the shell is executing the condition of an if- * statement, an and-or list, etc. to suppress the effect of the "errexit" and * "errreturn" options. */ static bool suppresserrexit = false, suppresserrreturn = false; /* state of currently executed loop */ static execstate_T execstate; /* exceptional jump to be done (other than "break") */ static exception_T exception; /* This flag is set when a special built-in is executed as such. */ bool special_builtin_executed; /* This flag is set while the "exec" built-in is executed. */ static bool exec_builtin_executed = false; /* True while executing auxiliary commands such as $PROMPT_COMMAND and * $COMMAND_NOT_FOUND_HANDLER. */ bool is_executing_auxiliary = false; /* the last assignment. */ static const assign_T *last_assign; /* a buffer for xtrace. * When assignments are performed while executing a simple command, the trace * is appended to this buffer. Each trace of an assignment must be prefixed * with a space to separate it with the previous one. The first space will be * trimmed when the buffer is flushed to the standard error. */ static xwcsbuf_T xtrace_buffer = { .contents = NULL }; /* Resets `execstate' to the initial state. */ void reset_execstate(bool reset_iteration) { execstate.loopnest = 0; execstate.breakloopnest = 0; execstate.noreturn = false; if (reset_iteration) execstate.iterating = false; } /* Saves the current `execstate' and returns it. * You typically call `reset_execstate' after calling this function. */ execstate_T *save_execstate(void) { execstate_T *save = xmalloc(sizeof execstate); *save = execstate; return save; } /* Restores `execstate' to `save' and frees `save'. */ void restore_execstate(execstate_T *save) { execstate = *save; free(save); } /* Disables the "return" built-in in the current `execstate'. */ void disable_return(void) { execstate.noreturn = true; } /* If we're returning, clear the flag. */ void cancel_return(void) { if (exception == E_RETURN) exception = E_NONE; } /* Returns true iff we're breaking/continuing/returning now. */ bool need_break(void) { return execstate.breakloopnest < execstate.loopnest || exception != E_NONE || is_interrupted(); } /* Executes the and-or lists. * If `finally_exit' is true, the shell exits after execution. */ void exec_and_or_lists(const and_or_T *a, bool finally_exit) { while (a != NULL && !need_break()) { if (!a->ao_async) exec_pipelines(a->ao_pipelines, finally_exit && !a->next); else exec_pipelines_async(a->ao_pipelines); a = a->next; } if (finally_exit) exit_shell(); } /* Executes the pipelines. */ void exec_pipelines(const pipeline_T *p, bool finally_exit) { for (bool first = true; p != NULL && !need_break(); p = p->next, first = false) { if (!first && p->pl_cond == (laststatus != Exit_SUCCESS)) continue; bool savesee = suppresserrexit, saveser = suppresserrreturn; bool suppress = p->pl_neg || p->next != NULL; suppresserrexit |= suppress; suppresserrreturn |= suppress; bool self = finally_exit && !doing_job_control_now && !p->next && !p->pl_neg && !any_trap_set && !shopt_pipefail; exec_commands(p->pl_commands, self ? E_SELF : E_NORMAL); if (p->pl_neg) { if (laststatus == Exit_SUCCESS) laststatus = Exit_FAILURE; else laststatus = Exit_SUCCESS; } suppresserrexit = savesee, suppresserrreturn = saveser; } if (finally_exit) exit_shell(); } /* Executes the pipelines asynchronously. */ void exec_pipelines_async(const pipeline_T *p) { if (p->next == NULL && !p->pl_neg) { exec_commands(p->pl_commands, E_ASYNC); return; } pid_t cpid = fork_and_reset(0, false, t_quitint); if (cpid > 0) { /* parent process: add a new job */ job_T *job = xmalloc(add(sizeof *job, sizeof *job->j_procs)); process_T *ps = job->j_procs; ps->pr_pid = cpid; ps->pr_status = JS_RUNNING; ps->pr_statuscode = 0; ps->pr_name = pipelines_to_wcs(p); job->j_pgid = doing_job_control_now ? cpid : 0; job->j_status = JS_RUNNING; job->j_statuschanged = true; job->j_legacy = false; #ifndef NDEBUG job->j_beingwaitedfor = false; #endif job->j_pcount = 1; set_active_job(job); add_job(shopt_curasync); laststatus = Exit_SUCCESS; lastasyncpid = cpid; } else if (cpid == 0) { /* child process: execute the commands and then exit */ maybe_redirect_stdin_to_devnull(); exec_pipelines(p, true); assert(false); } else { /* fork failure */ laststatus = Exit_NOEXEC; } } /* Executes the if command */ void exec_if(const command_T *c, bool finally_exit) { assert(c->c_type == CT_IF); for (const ifcommand_T *cmds = c->c_ifcmds; cmds != NULL; cmds = cmds->next) { if (need_break()) goto done; if (exec_condition(cmds->ic_condition)) { exec_and_or_lists(cmds->ic_commands, finally_exit); assert(!finally_exit); return; } } laststatus = Exit_SUCCESS; done: if (finally_exit) exit_shell(); } /* Executes the condition of an if/while/until command. */ bool exec_condition(const and_or_T *c) { if (c == NULL) return true; bool savesee = suppresserrexit, saveser = suppresserrreturn; suppresserrexit = suppresserrreturn = true; exec_and_or_lists(c, false); suppresserrexit = savesee, suppresserrreturn = saveser; return laststatus == Exit_SUCCESS; } /* Executes the for command. */ void exec_for(const command_T *c, bool finally_exit) { assert(c->c_type == CT_FOR); execstate.loopnest++; execstate.breakloopnest = execstate.loopnest; int count; void **words; if (c->c_forwords != NULL) { /* expand the words between "in" and "do" of the for command. */ if (!expand_line(c->c_forwords, &count, &words)) { laststatus = Exit_EXPERROR; apply_errexit_errreturn(NULL); goto finish; } } else { /* no "in" keyword in the for command: use the positional parameters */ struct get_variable_T v = get_variable(L"@"); assert(v.type == GV_ARRAY && v.values != NULL); save_get_variable_values(&v); count = (int) v.count; words = v.values; } #define CHECK_LOOP \ if (execstate.breakloopnest < execstate.loopnest) { \ goto done; \ } else if (exception == E_CONTINUE) { \ exception = E_NONE; \ continue; \ } else if (exception != E_NONE) { \ goto done; \ } else if (is_interrupted()) { \ goto done; \ } else (void) 0 int i; for (i = 0; i < count; i++) { if (!set_variable(c->c_forname, words[i], shopt_forlocal && !posixly_correct ? SCOPE_LOCAL : SCOPE_GLOBAL, false)) { laststatus = Exit_ASSGNERR; goto done; } exec_and_or_lists(c->c_forcmds, finally_exit && i + 1 == count); if (c->c_forcmds == NULL) handle_signals(); CHECK_LOOP; } done: while (++i < count) /* free unused words */ free(words[i]); free(words); if (count == 0 && c->c_forcmds != NULL) laststatus = Exit_SUCCESS; finish: execstate.loopnest--; if (finally_exit) exit_shell(); } /* Executes the while/until command. */ /* The exit status of a while/until command is that of `c_whlcmds' executed * last. If `c_whlcmds' is not executed at all, the status is 0 regardless of * `c_whlcond'. */ void exec_while(const command_T *c, bool finally_exit) { assert(c->c_type == CT_WHILE); execstate.loopnest++; execstate.breakloopnest = execstate.loopnest; int status = Exit_SUCCESS; for (;;) { bool cond = exec_condition(c->c_whlcond); if (c->c_whlcond == NULL) handle_signals(); CHECK_LOOP; if (cond != c->c_whltype) break; if (c->c_whlcmds != NULL) { exec_and_or_lists(c->c_whlcmds, false); status = laststatus; } if (c->c_whlcmds == NULL) handle_signals(); CHECK_LOOP; } laststatus = status; done: execstate.loopnest--; if (finally_exit) exit_shell(); } #undef CHECK_LOOP /* Executes the case command. */ void exec_case(const command_T *c, bool finally_exit) { assert(c->c_type == CT_CASE); wchar_t *word = expand_single_and_unescape( c->c_casword, TT_SINGLE, true, false); if (word == NULL) goto fail; for (const caseitem_T *ci = c->c_casitems; ci != NULL; ci = ci->next) { for (void **pats = ci->ci_patterns; *pats != NULL; pats++) { wchar_t *pattern = expand_single(*pats, TT_SINGLE, true, false); if (pattern == NULL) goto fail; bool match = match_pattern(word, pattern); free(pattern); if (match) { if (ci->ci_commands != NULL) { exec_and_or_lists(ci->ci_commands, finally_exit); goto done; } else { goto success; } } } } success: laststatus = Exit_SUCCESS; done: free(word); if (finally_exit) exit_shell(); return; fail: laststatus = Exit_EXPERROR; apply_errexit_errreturn(NULL); goto done; } /* Executes the function definition. */ void exec_funcdef(const command_T *c, bool finally_exit) { assert(c->c_type == CT_FUNCDEF); wchar_t *funcname = expand_single_and_unescape( c->c_funcname, TT_SINGLE, true, false); if (funcname != NULL) { if (define_function(funcname, c->c_funcbody)) laststatus = Exit_SUCCESS; else laststatus = Exit_ASSGNERR; free(funcname); } else { laststatus = Exit_EXPERROR; } if (finally_exit) exit_shell(); } /* Executes the commands in a pipeline. */ void exec_commands(command_T *c, exec_T type) { size_t count; pid_t pgid; command_T *cc; job_T *job; process_T *ps, *pp; pipeinfo_T pinfo = PIPEINFO_INIT; /* increment the reference count of `c' to prevent `c' from being freed * during execution. */ c = comsdup(c); /* count the number of the commands */ count = 0; for (cc = c; cc != NULL; cc = cc->next) count++; assert(count > 0); job = xmallocs(sizeof *job, count, sizeof *job->j_procs); ps = job->j_procs; /* execute the commands */ pgid = 0, cc = c, pp = ps; do { pid_t pid; next_pipe(&pinfo, cc->next != NULL); pid = exec_process(cc, (type == E_SELF && cc->next != NULL) ? E_NORMAL : type, &pinfo, pgid); pp->pr_pid = pid; if (pid != 0) { pp->pr_status = JS_RUNNING; pp->pr_statuscode = 0; } else { pp->pr_status = JS_DONE; pp->pr_statuscode = laststatus; } pp->pr_name = NULL; /* name is given later */ if (pgid == 0) pgid = pid; cc = cc->next, pp++; } while (cc != NULL); assert(type != E_SELF); /* `exec_process' doesn't return for E_SELF */ assert(pinfo.pi_tonextfds[PIPE_IN] < 0); assert(pinfo.pi_tonextfds[PIPE_OUT] < 0); if (pinfo.pi_fromprevfd >= 0) xclose(pinfo.pi_fromprevfd); /* close leftover pipe */ if (pgid == 0) { /* nothing more to do if we didn't fork */ free(job); } else { job->j_pgid = doing_job_control_now ? pgid : 0; job->j_status = JS_RUNNING; job->j_statuschanged = true; job->j_legacy = false; #ifndef NDEBUG job->j_beingwaitedfor = false; #endif job->j_pcount = count; set_active_job(job); if (type == E_NORMAL) { wait_for_job(ACTIVE_JOBNO, doing_job_control_now, false, false); if (doing_job_control_now) put_foreground(shell_pgid); laststatus = calc_status_of_job(job); } else { assert(type == E_ASYNC); laststatus = Exit_SUCCESS; lastasyncpid = ps[count - 1].pr_pid; } if (job->j_status != JS_DONE) { for (cc = c, pp = ps; cc != NULL; cc = cc->next, pp++) pp->pr_name = command_to_wcs(cc, false); add_job(type == E_NORMAL || shopt_curasync); } else { notify_signaled_job(ACTIVE_JOBNO); remove_job(ACTIVE_JOBNO); } } handle_signals(); apply_errexit_errreturn(c); comsfree(c); } /* Returns true if the shell should exit because of the `errexit' option. */ bool is_errexit_condition(void) { if (!shopt_errexit || suppresserrexit) return false; if (laststatus == Exit_SUCCESS) return false; #if YASH_ENABLE_LINEEDIT if (le_state & LE_STATE_COMPLETING) return false; #endif return true; } /* Returns true if the shell should return because of the `errreturn' option. */ bool is_errreturn_condition(void) { if (!shopt_errreturn || suppresserrreturn || execstate.noreturn) return false; return laststatus != Exit_SUCCESS; } /* Tests the current condition for "errexit" and "errreturn" and then performs * exit or return if applicable. */ void apply_errexit_errreturn(const command_T *c) { if (is_errexit_condition() && is_err_condition_for(c)) exit_shell_with_status(laststatus); if (is_errreturn_condition() && is_err_condition_for(c)) exception = E_RETURN; } /* Returns true if "errexit" and "errreturn" should be applied to the given * command. */ bool is_err_condition_for(const command_T *c) { if (c == NULL) return true; /* If this is a multi-command pipeline, the commands are executed in * subshells. Otherwise, we need to check the type of the command. */ if (c->next != NULL) return true; switch (c->c_type) { case CT_SIMPLE: case CT_SUBSHELL: #if YASH_ENABLE_DOUBLE_BRACKET case CT_BRACKET: #endif case CT_FUNCDEF: return true; case CT_GROUP: case CT_IF: case CT_FOR: case CT_WHILE: case CT_CASE: return false; } assert(false); } /* Updates the contents of the `pipeinfo_T' to proceed to the next process * execution. `pi->pi_fromprevfd' and `pi->pi_tonextfds[PIPE_OUT]' are closed, * `pi->pi_tonextfds[PIPE_IN]' is moved to `pi->pi_fromprevfd', and, * if `next' is true, a new pipe is opened in `pi->pi_tonextfds'; otherwise, * `pi->pi_tonextfds' is assigned -1. * Returns true iff successful. */ void next_pipe(pipeinfo_T *pi, bool next) { if (pi->pi_fromprevfd >= 0) xclose(pi->pi_fromprevfd); if (pi->pi_tonextfds[PIPE_OUT] >= 0) xclose(pi->pi_tonextfds[PIPE_OUT]); pi->pi_fromprevfd = pi->pi_tonextfds[PIPE_IN]; if (next) { if (pipe(pi->pi_tonextfds) < 0) goto fail; /* The pipe's FDs must not be 0 or 1, or they may be overridden by each * other when we move the pipe to the standard input/output later. So, * if they are 0 or 1, we move them to bigger numbers. */ int origin = pi->pi_tonextfds[PIPE_IN]; int origout = pi->pi_tonextfds[PIPE_OUT]; if (origin < 2 || origout < 2) { if (origin < 2) pi->pi_tonextfds[PIPE_IN] = dup(origin); if (origout < 2) pi->pi_tonextfds[PIPE_OUT] = dup(origout); if (origin < 2) xclose(origin); if (origout < 2) xclose(origout); if (pi->pi_tonextfds[PIPE_IN] < 0) { xclose(pi->pi_tonextfds[PIPE_OUT]); goto fail; } if (pi->pi_tonextfds[PIPE_OUT] < 0) { xclose(pi->pi_tonextfds[PIPE_IN]); goto fail; } } } else { pi->pi_tonextfds[PIPE_IN] = pi->pi_tonextfds[PIPE_OUT] = -1; } return; fail: pi->pi_tonextfds[PIPE_IN] = pi->pi_tonextfds[PIPE_OUT] = -1; xerror(errno, Ngt("cannot open a pipe")); } /* Executes the command. * If job control is active, the child process's process group ID is set to * `pgid'. If `pgid' is 0, the child process's process ID is used as the process * group ID. * If the child process forked successfully, its process ID is returned. * If the command was executed without forking, `laststatus' is set to the exit * status of the command and 0 is returned. * if `type' is E_SELF, this function never returns. */ pid_t exec_process( command_T *restrict c, exec_T type, pipeinfo_T *restrict pi, pid_t pgid) { bool finally_exit; /* never return? */ int argc; void **argv = NULL; char *argv0 = NULL; pid_t cpid = 0; current_lineno = c->c_lineno; /* fork first if `type' is E_ASYNC, the command type is subshell, * or there is a pipe. */ finally_exit = (type == E_SELF); if (finally_exit) { if (c->c_type == CT_SUBSHELL) /* No command follows this subshell command, so we can execute the * subshell directly in this process. */ become_child(false); } else { if (type == E_ASYNC || c->c_type == CT_SUBSHELL || pi->pi_fromprevfd >= 0 || pi->pi_tonextfds[PIPE_OUT] >= 0) { sigtype_T sigtype = (type == E_ASYNC) ? t_quitint : 0; cpid = fork_and_reset(pgid, type == E_NORMAL, sigtype); if (cpid != 0) goto done; finally_exit = true; } } lastcmdsubstatus = Exit_SUCCESS; /* connect pipes and close leftovers */ connect_pipes(pi); if (c->c_type == CT_SIMPLE) { if (!expand_line(c->c_words, &argc, &argv)) { laststatus = Exit_EXPERROR; goto done; } if (is_interrupted()) { plfree(argv, free); goto done; } if (argc == 0) { argv0 = NULL; } else { argv0 = malloc_wcstombs(argv[0]); if (argv0 == NULL) argv0 = xstrdup(""); } } /* `argc' and `argv' are used only for `CT_SIMPLE'. */ /* open redirections */ savefd_T *savefd; if (!open_redirections(c->c_redirs, &savefd)) { /* On redirection error, the command is not executed. */ laststatus = Exit_REDIRERR; apply_errexit_errreturn(NULL); if (posixly_correct && !is_interactive_now && c->c_type == CT_SIMPLE && argc > 0 && is_special_builtin(argv0)) finally_exit = true; goto done2; } if (type == E_ASYNC && pi->pi_fromprevfd < 0) maybe_redirect_stdin_to_devnull(); if (c->c_type != CT_SIMPLE) { exec_nonsimple_command(c, finally_exit && savefd == NULL); goto done1; } last_assign = c->c_assigns; if (argc == 0) { /* if there is no command word, just perform assignments */ if (do_assignments(c->c_assigns, false, shopt_allexport)) { laststatus = lastcmdsubstatus; } else { laststatus = Exit_ASSGNERR; if (!is_interactive_now) finally_exit = true; } print_xtrace(NULL); goto done2; } commandinfo_T cmdinfo; bool temp; /* check if the command is a special built-in or a function and determine * whether we have to open a temporary environment. */ search_command(argv0, argv[0], &cmdinfo, SCT_BUILTIN | SCT_FUNCTION); special_builtin_executed = (cmdinfo.type == CT_SPECIALBUILTIN); temp = c->c_assigns != NULL && !special_builtin_executed; if (temp) open_new_environment(true); /* perform the assignments */ if (!do_assignments(c->c_assigns, temp, true)) { /* On assignment error, the command is not executed. */ print_xtrace(NULL); laststatus = Exit_ASSGNERR; if (!is_interactive_now) finally_exit = true; goto done3; } print_xtrace(argv); /* find command path */ if (cmdinfo.type == CT_NONE) { search_command(argv0, argv[0], &cmdinfo, SCT_EXTERNAL | SCT_BUILTIN | SCT_CHECK); if (cmdinfo.type == CT_NONE) { if (!posixly_correct && command_not_found_handler(argv)) goto done3; if (wcschr(argv[0], L'/') != NULL) { cmdinfo.type = CT_EXTERNALPROGRAM; cmdinfo.ci_path = argv0; } } } /* create a child process to execute the external command */ if (cmdinfo.type == CT_EXTERNALPROGRAM && !finally_exit) { assert(type == E_NORMAL); cpid = fork_and_reset(pgid, true, t_leave); if (cpid != 0) goto done3; finally_exit = true; } /* execute! */ bool finally_exit2; switch (cmdinfo.type) { case CT_EXTERNALPROGRAM: finally_exit2 = true; break; case CT_FUNCTION: finally_exit2 = (finally_exit && /* !temp && */ savefd == NULL); break; default: finally_exit2 = false; break; } exec_simple_command(&cmdinfo, argc, argv0, argv, finally_exit2); /* Redirections are not undone after a successful "exec" command: * remove the saved data of file descriptors. */ if (exec_builtin_executed && laststatus == Exit_SUCCESS) { clear_savefd(savefd); savefd = NULL; } exec_builtin_executed = false; done3: if (temp) close_current_environment(); done2: if (c->c_type == CT_SIMPLE) plfree(argv, free), free(argv0); done1: undo_redirections(savefd); done: if (cpid < 0) { laststatus = Exit_NOEXEC; cpid = 0; } if (finally_exit) exit_shell(); return cpid; } /* Connects the pipe(s) and closes the pipes left. */ void connect_pipes(pipeinfo_T *pi) { if (pi->pi_fromprevfd >= 0) { xdup2(pi->pi_fromprevfd, STDIN_FILENO); xclose(pi->pi_fromprevfd); } if (pi->pi_tonextfds[PIPE_OUT] >= 0) { xdup2(pi->pi_tonextfds[PIPE_OUT], STDOUT_FILENO); xclose(pi->pi_tonextfds[PIPE_OUT]); } if (pi->pi_tonextfds[PIPE_IN] >= 0) xclose(pi->pi_tonextfds[PIPE_IN]); } /* Forks a subshell and does some settings. * If job control is active, the child process's process group ID is set to * `pgid'. If `pgid' is 0, the child process's process ID is used as the process * group ID.. * If `pgid' is negative or job control is inactive, the process group ID is not * changed. * If job control is active and `fg' is true, the child process becomes a * foreground process. * `sigtype' specifies the settings of signals in the child. * `sigtype' is a bitwise OR of the followings: * t_quitint: SIGQUIT & SIGINT are ignored if the parent's job control is off * t_tstp: SIGTSTP is ignored if the parent is job-controlling * t_leave: Don't clear traps and shell FDs. Restore the signal mask for * SIGCHLD. Don't reset `execstate'. This option must be used iff the * shell is going to `exec' to an external program. * Returns the return value of `fork'. */ pid_t fork_and_reset(pid_t pgid, bool fg, sigtype_T sigtype) { sigset_t savemask; if (sigtype & (t_quitint | t_tstp)) { /* block all signals to prevent the race condition */ sigset_t all; sigfillset(&all); sigemptyset(&savemask); sigprocmask(SIG_BLOCK, &all, &savemask); } pid_t cpid = fork(); if (cpid != 0) { if (cpid < 0) { /* fork failure */ xerror(errno, Ngt("cannot make a child process")); } else { /* parent process */ if (doing_job_control_now && pgid >= 0) setpgid(cpid, pgid); } if (sigtype & (t_quitint | t_tstp)) sigprocmask(SIG_SETMASK, &savemask, NULL); } else { /* child process */ bool save_doing_job_control_now = doing_job_control_now; if (save_doing_job_control_now && pgid >= 0) { setpgid(0, pgid); if (pgid == 0) pgid = getpgrp(); shell_pgid = pgid; if (fg) put_foreground(pgid); } if (sigtype & t_quitint) if (!save_doing_job_control_now) ignore_sigquit_and_sigint(); if (sigtype & t_tstp) if (save_doing_job_control_now) ignore_sigtstp(); become_child(sigtype & t_leave); /* signal mask is restored here */ } return cpid; } /* Resets traps, signal handlers, etc. for the current process to become a * subshell. See `fork_and_reset' for the meaning of the `leave' argument. */ void become_child(bool leave) { if (leave) { clear_exit_trap(); } else { phantomize_traps(); neglect_all_jobs(); #if YASH_ENABLE_HISTORY close_history_file(); #endif reset_execstate(true); } restore_signals(leave); /* signal mask is restored here */ clear_shellfds(leave); is_interactive_now = false; suppresserrreturn = false; exitstatus = -1; } /* Searches for a command. * The result is assigned to `*ci'. * `name' and `wname' must contain the same string value. * If the SCT_ALL flag is not set: * * a function whose name contains a slash cannot be found * * a regular built-in cannot be found in the POSIXly correct mode if the * SCT_EXTERNAL flag is not set either. * If the SCT_EXTERNAL flag is set, the SCT_CHECK flag is not set, and `name' * contains a slash, the external command of the given `name' is always found. */ void search_command( const char *restrict name, const wchar_t *restrict wname, commandinfo_T *restrict ci, enum srchcmdtype_T type) { bool slash = wcschr(wname, L'/') != NULL; const builtin_T *bi; if (!slash && (type & SCT_BUILTIN)) bi = get_builtin(name); else bi = NULL; if (bi != NULL && bi->type == BI_SPECIAL) { ci->type = CT_SPECIALBUILTIN; ci->ci_builtin = bi->body; return; } if ((type & SCT_FUNCTION) && (!slash || (type & SCT_ALL))) { command_T *funcbody = get_function(wname); if (funcbody != NULL) { ci->type = CT_FUNCTION; ci->ci_function = funcbody; return; } } if (bi != NULL) { if (bi->type == BI_SEMISPECIAL) { ci->type = CT_SEMISPECIALBUILTIN; ci->ci_builtin = bi->body; return; } else if (!posixly_correct) { goto regular_builtin; } } if (slash) { if (type & SCT_EXTERNAL) { if (!(type & SCT_CHECK) || is_executable_regular(name)) { ci->type = CT_EXTERNALPROGRAM; ci->ci_path = name; return; } } } else { if ((type & SCT_EXTERNAL) || (bi != NULL && (type & SCT_ALL))) { const char *cmdpath; if (type & SCT_STDPATH) cmdpath = get_command_path_default(name); else cmdpath = get_command_path(name, false); if (cmdpath != NULL) { if (bi != NULL) { regular_builtin: assert(bi->type == BI_REGULAR); ci->type = CT_REGULARBUILTIN; ci->ci_builtin = bi->body; } else { ci->type = CT_EXTERNALPROGRAM; ci->ci_path = cmdpath; } return; } } } /* command not found... */ ci->type = CT_NONE; ci->ci_path = NULL; return; } /* Returns true iff the specified command is a special built-in. */ bool is_special_builtin(const char *cmdname) { const builtin_T *bi = get_builtin(cmdname); return bi != NULL && bi->type == BI_SPECIAL; } /* Executes $COMMAND_NOT_FOUND_HANDLER if any. * `argv' is set to the positional parameters of the environment in which the * handler is executed. * Returns true iff the hander was executed and $HANDLED was set non-empty. */ bool command_not_found_handler(void *const *argv) { static bool handling = false; if (handling) return false; /* don't allow reentrance */ handling = true; bool handled; int result; open_new_environment(false); set_positional_parameters(argv); set_variable(L VAR_HANDLED, xwcsdup(L""), SCOPE_LOCAL, false); result = exec_variable_as_auxiliary_(VAR_COMMAND_NOT_FOUND_HANDLER); if (result >= 0) { const wchar_t *handledv = getvar(L VAR_HANDLED); handled = (handledv != NULL && handledv[0] != L'\0'); } else { handled = false; } close_current_environment(); handling = false; if (handled) { laststatus = result; return true; } else { return false; } } /* Executes the specified command whose type is not `CT_SIMPLE'. * The redirections for the command is not performed in this function. * For CT_SUBSHELL, this function must be called in an already-forked subshell. */ void exec_nonsimple_command(command_T *c, bool finally_exit) { switch (c->c_type) { case CT_SIMPLE: assert(false); case CT_SUBSHELL: case CT_GROUP: exec_and_or_lists(c->c_subcmds, finally_exit); break; case CT_IF: exec_if(c, finally_exit); break; case CT_FOR: exec_for(c, finally_exit); break; case CT_WHILE: exec_while(c, finally_exit); break; case CT_CASE: exec_case(c, finally_exit); break; #if YASH_ENABLE_DOUBLE_BRACKET case CT_BRACKET: laststatus = exec_double_bracket(c); if (finally_exit) exit_shell(); break; #endif /* YASH_ENABLE_DOUBLE_BRACKET */ case CT_FUNCDEF: exec_funcdef(c, finally_exit); break; } } /* Executes the simple command. */ /* `argv0' is the multibyte version of `argv[0]' */ void exec_simple_command( const commandinfo_T *ci, int argc, char *argv0, void **argv, bool finally_exit) { assert(plcount(argv) == (size_t) argc); switch (ci->type) { case CT_NONE: xerror(0, Ngt("no such command `%s'"), argv0); laststatus = Exit_NOTFOUND; break; case CT_EXTERNALPROGRAM: assert(finally_exit); exec_external_program(ci->ci_path, argc, argv0, argv, environ); break; case CT_SPECIALBUILTIN: case CT_SEMISPECIALBUILTIN: case CT_REGULARBUILTIN: yash_error_message_count = 0; const wchar_t *savecbn = current_builtin_name; current_builtin_name = argv[0]; laststatus = ci->ci_builtin(argc, argv); current_builtin_name = savecbn; break; case CT_FUNCTION: exec_function_body(ci->ci_function, &argv[1], finally_exit, false); break; } if (finally_exit) exit_shell(); } /* Executes the external program. * path: path to the program to be executed * argc: number of strings in `argv' * argv0: multibyte version of `argv[0]' * argv: pointer to an array of pointers to wide strings that are passed to * the program */ void exec_external_program( const char *path, int argc, char *argv0, void **argv, char **envs) { char *mbsargv[argc + 1]; mbsargv[0] = argv0; for (int i = 1; i < argc; i++) { mbsargv[i] = malloc_wcstombs(argv[i]); if (mbsargv[i] == NULL) mbsargv[i] = xstrdup(""); } mbsargv[argc] = NULL; restore_signals(true); xexecve(path, mbsargv, envs); int saveerrno = errno; if (saveerrno != ENOEXEC) { if (saveerrno == EACCES && is_directory(path)) saveerrno = EISDIR; xerror(saveerrno, strcmp(mbsargv[0], path) == 0 ? Ngt("cannot execute command `%s'") : Ngt("cannot execute command `%s' (%s)"), argv0, path); } else if (saveerrno != ENOENT) { exec_fall_back_on_sh(argc, mbsargv, envs, path); } laststatus = (saveerrno == ENOENT) ? Exit_NOTFOUND : Exit_NOEXEC; set_signals(); for (int i = 1; i < argc; i++) free(mbsargv[i]); } /* Returns a pointer to the xtrace buffer. * The buffer is initialized if not. */ xwcsbuf_T *get_xtrace_buffer(void) { if (xtrace_buffer.contents == NULL) wb_init(&xtrace_buffer); return &xtrace_buffer; } /* Prints a trace if the "xtrace" option is on. */ void print_xtrace(void *const *argv) { bool tracevars = xtrace_buffer.contents != NULL && xtrace_buffer.length > 0; if (shopt_xtrace && (!is_executing_auxiliary || shopt_traceall) && (tracevars || argv != NULL) #if YASH_ENABLE_LINEEDIT && !(le_state & LE_STATE_ACTIVE) #endif ) { bool first = true; struct promptset_T prompt = get_prompt(4); print_prompt(prompt.main); print_prompt(prompt.styler); if (tracevars) { fprintf(stderr, "%ls", xtrace_buffer.contents + 1); first = false; } if (argv != NULL) { for (void *const *a = argv; *a != NULL; a++) { if (!first) fputc(' ', stderr); first = false; wchar_t *quoted = quote_as_word(*a); fprintf(stderr, "%ls", quoted); free(quoted); } } fputc('\n', stderr); print_prompt(PROMPT_RESET); free_prompt(prompt); } if (xtrace_buffer.contents != NULL) { wb_destroy(&xtrace_buffer); xtrace_buffer.contents = NULL; } } /* Executes the specified command as a shell script by `exec'ing a shell. * `path' is the full path to the script file. * Returns iff failed to `exec'. */ void exec_fall_back_on_sh( int argc, char *const *argv, char *const *envp, const char *path) { assert(argv[argc] == NULL); char *args[argc + 3]; size_t index = 0; args[index++] = "sh"; args[index++] = (char *) "-"; if (strcmp(path, "--") == 0) args[index++] = "./--"; else args[index++] = (char *) path; for (int i = 1; i < argc; i++) args[index++] = argv[i]; args[index] = NULL; #if HAVE_PROC_SELF_EXE xexecve("/proc/self/exe", args, envp); #elif HAVE_PROC_CURPROC_FILE xexecve("/proc/curproc/file", args, envp); #elif HAVE_PROC_OBJECT_AOUT char *objpath = malloc_printf( "/proc/%jd/object/a.out", (intmax_t) getpid()); xexecve(objpath, args, envp); free(objpath); #elif HAVE_PATHS_H && defined _PATH_BSHELL xexecve(_PATH_BSHELL, args, envp); #endif const char *shpath = get_command_path("sh", false); if (shpath != NULL) xexecve(shpath, args, envp); else errno = ENOENT; xerror(errno, Ngt("cannot invoke a new shell to execute script `%s'"), argv[0]); } /* Executes the specified command as a function. * `args' are the arguments to the function, which are wide strings cast to * (void *). * If `complete' is true, `set_completion_variables' will be called after a new * variable environment was opened before the function body is executed. */ void exec_function_body( command_T *body, void *const *args, bool finally_exit, bool complete) { execstate_T *saveexecstate = save_execstate(); reset_execstate(false); bool saveser = suppresserrreturn; suppresserrreturn = false; open_new_environment(false); set_positional_parameters(args); #if YASH_ENABLE_LINEEDIT if (complete) set_completion_variables(); #else (void) complete; #endif exec_commands(body, finally_exit ? E_SELF : E_NORMAL); close_current_environment(); cancel_return(); suppresserrreturn = saveser; restore_execstate(saveexecstate); } /* Calls `execve' until it doesn't return EINTR. */ int xexecve(const char *path, char *const *argv, char *const *envp) { do execve(path, argv, envp); while (errno == EINTR); return -1; } /* Executes the command substitution and returns the string to substitute with. * This function blocks until the command finishes. * The return value is a newly-malloced string without a trailing newline. * NULL is returned on error. */ wchar_t *exec_command_substitution(const embedcmd_T *cmdsub) { int pipefd[2]; pid_t cpid; if (cmdsub->is_preparsed ? cmdsub->value.preparsed == NULL : cmdsub->value.unparsed[0] == L'\0') /* empty command */ return xwcsdup(L""); /* open a pipe to receive output from the command */ if (pipe(pipefd) < 0) { xerror(errno, Ngt("cannot open a pipe for the command substitution")); return NULL; } /* If the child is stopped by SIGTSTP, it can never be resumed and * the shell will be stuck. So we specify the `t_tstp' flag to prevent the * child from being stopped by SIGTSTP. */ cpid = fork_and_reset(-1, false, t_tstp); if (cpid < 0) { /* fork failure */ xclose(pipefd[PIPE_IN]); xclose(pipefd[PIPE_OUT]); lastcmdsubstatus = Exit_NOEXEC; return NULL; } else if (cpid > 0) { /* parent process */ FILE *f; xclose(pipefd[PIPE_OUT]); f = fdopen(pipefd[PIPE_IN], "r"); if (f == NULL) { xerror(errno, Ngt("cannot open a pipe for the command substitution")); xclose(pipefd[PIPE_IN]); lastcmdsubstatus = Exit_NOEXEC; return NULL; } /* read output from the command */ xwcsbuf_T buf; wint_t c; wb_init(&buf); while ((c = fgetwc(f)) != WEOF) wb_wccat(&buf, c); fclose(f); /* wait for the child to finish */ int savelaststatus = laststatus; wait_for_child(cpid, 0, false); lastcmdsubstatus = laststatus; laststatus = savelaststatus; /* trim trailing newlines and return */ size_t len = buf.length; while (len > 0 && buf.contents[len - 1] == L'\n') len--; return wb_towcs(wb_truncate(&buf, len)); } else { /* child process */ xclose(pipefd[PIPE_IN]); if (pipefd[PIPE_OUT] != STDOUT_FILENO) { /* connect the pipe */ if (xdup2(pipefd[PIPE_OUT], STDOUT_FILENO) < 0) exit(Exit_NOEXEC); xclose(pipefd[PIPE_OUT]); } if (cmdsub->is_preparsed) exec_and_or_lists(cmdsub->value.preparsed, true); else exec_wcs(cmdsub->value.unparsed, gt("command substitution"), true); assert(false); } } /* Executes commands in the given array of wide strings (iterative execution). * The strings are parsed and executed one by one. * If the iteration is interrupted by the "break -i" command, the remaining * elements are not executed. * `codename' is passed to `exec_wcs' as the command name. * Returns the exit status of the executed command (or zero if none executed). * When this function returns, `laststatus' is restored to the original value.*/ int exec_iteration(void *const *commands, const char *codename) { int savelaststatus = laststatus, commandstatus = Exit_SUCCESS; bool saveiterating = execstate.iterating; execstate.iterating = true; for (void *const *command = commands; *command != NULL; command++) { exec_wcs(*command, codename, false); commandstatus = laststatus; laststatus = savelaststatus; switch (exception) { case E_BREAK_ITERATION: exception = E_NONE; /* falls thru! */ default: goto done; case E_CONTINUE_ITERATION: exception = E_NONE; /* falls thru! */ case E_NONE: break; } if (execstate.breakloopnest < execstate.loopnest || is_interrupted()) goto done; } done: execstate.iterating = saveiterating; return commandstatus; } /* Executes the value of the specified variable. * The variable value is parsed as commands. * If the `varname' names an array, every element of the array is executed (but * if the iteration is interrupted by the "break -i" command, the remaining * elements are not executed). * `codename' is passed to `exec_wcs' as the command name. * Returns the exit status of the executed command (or zero if none executed, or * -1 if the variable is unset). * When this function returns, `laststatus' is restored to the original value.*/ int exec_variable_as_commands(const wchar_t *varname, const char *codename) { struct get_variable_T gv = get_variable(varname); switch (gv.type) { case GV_NOTFOUND: return -1; case GV_SCALAR: case GV_ARRAY: break; case GV_ARRAY_CONCAT: /* should execute the concatenated value, but is not supported now*/ return -1; } /* copy the array values in case they are unset during execution */ save_get_variable_values(&gv); /* prevent "break" and "continue" */ execstate_T *saveexecstate = save_execstate(); // reset_execstate(false); /* don't reset `execstate.noreturn' */ execstate.loopnest = 0; int result = exec_iteration(gv.values, codename); restore_execstate(saveexecstate); plfree(gv.values, free); return result; } /* Calls `exec_variable_as_commands' with `is_executing_auxiliary' set to true. */ int exec_variable_as_auxiliary(const wchar_t *varname, const char *codename) { bool save_executing_auxiliary = is_executing_auxiliary; is_executing_auxiliary = true; int result = exec_variable_as_commands(varname, codename); is_executing_auxiliary = save_executing_auxiliary; return result; } #if YASH_ENABLE_LINEEDIT /* Autoloads the specified file to load a completion function definition. * String `cmdname', which may be NULL, is used as the only positional parameter * during script execution. * Returns true iff a file was autoloaded. */ bool autoload_completion_function_file( const wchar_t *filename, const wchar_t *cmdname) { char *mbsfilename = malloc_wcstombs(filename); if (mbsfilename == NULL) return false; char *path = which(mbsfilename, get_path_array(PA_LOADPATH), is_readable_regular); if (path == NULL) { le_compdebug("file \"%s\" was not found in $YASH_LOADPATH", mbsfilename); free(mbsfilename); return false; } int fd = move_to_shellfd(open(path, O_RDONLY)); if (fd < 0) { le_compdebug("cannot open file \"%s\"", path); free(path); return false; } execstate_T *saveexecstate = save_execstate(); int savelaststatus = laststatus; bool saveposix = posixly_correct; reset_execstate(true); posixly_correct = false; open_new_environment(false); set_positional_parameters((void *[]) { (void *) cmdname, NULL }); le_compdebug("executing file \"%s\" (autoload)", path); exec_input(fd, mbsfilename, 0); le_compdebug("finished executing file \"%s\"", path); close_current_environment(); posixly_correct = saveposix; laststatus = savelaststatus; cancel_return(); restore_execstate(saveexecstate); remove_shellfd(fd); xclose(fd); free(path); free(mbsfilename); return true; } /* Calls the function whose name is `funcname' as a completion function. * Returns false if no such function has been defined. */ bool call_completion_function(const wchar_t *funcname) { command_T *func = get_function(funcname); if (func == NULL) { le_compdebug("completion function \"%ls\" is not defined", funcname); return false; } execstate_T *saveexecstate = save_execstate(); int savelaststatus = laststatus; bool saveposix = posixly_correct, saveerrreturn = shopt_errreturn; reset_execstate(true); posixly_correct = false, shopt_errreturn = false; le_compdebug("executing completion function \"%ls\"", funcname); exec_function_body(func, (void *[]) { NULL }, false, true); le_compdebug("finished executing completion function \"%ls\"", funcname); le_compdebug(" with the exit status of %d", laststatus); posixly_correct = saveposix, shopt_errreturn = saveerrreturn; laststatus = savelaststatus; cancel_return(); restore_execstate(saveexecstate); return true; } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-ins **********/ static int exec_builtin_2(int argc, void **argv, const wchar_t *as, bool clear) __attribute__((nonnull(2))); static int command_builtin_execute( int argc, void **argv, enum srchcmdtype_T type) __attribute__((nonnull)); static bool print_command_info(const wchar_t *commandname, enum srchcmdtype_T type, bool alias, bool keyword, bool humanfriendly) __attribute__((nonnull)); static void print_command_absolute_path( const char *name, const char *path, bool humanfriendly) __attribute__((nonnull)); /* Options for the "break", "continue" and "eval" built-ins. */ const struct xgetopt_T iter_options[] = { { L'i', L"iteration", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* Options for the "return" built-in. */ const struct xgetopt_T return_options[] = { { L'n', L"no-return", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "return" built-in, which accepts the following option: * -n: don't return from a function. */ int return_builtin(int argc, void **argv) { bool noreturn = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, return_options, 0)) != NULL) { switch (opt->shortopt) { case L'n': noreturn = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (!validate_operand_count(argc - xoptind, 0, 1)) return special_builtin_error(Exit_ERROR); int status; const wchar_t *statusstr = ARGV(xoptind); if (statusstr != NULL) { if (!xwcstoi(statusstr, 10, &status) || status < 0) { xerror(0, Ngt("`%ls' is not a valid integer"), statusstr); status = Exit_ERROR; special_builtin_error(status); /* return anyway */ } } else { status = (savelaststatus >= 0) ? savelaststatus : laststatus; } if (!noreturn) { if (execstate.noreturn && is_interactive_now) { xerror(0, Ngt("cannot be used in the interactive mode")); return Exit_FAILURE; } exception = E_RETURN; } return status; } #if YASH_ENABLE_HELP const char return_help[] = Ngt( "return from a function or script" ); const char return_syntax[] = Ngt( "\treturn [-n] [exit_status]\n" ); #endif /* The "break"/"continue" built-in, which accepts the following option: * -i: iterative execution */ int break_builtin(int argc, void **argv) { bool iter = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, iter_options, 0)) != NULL) { switch (opt->shortopt) { case L'i': iter = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (!validate_operand_count(argc - xoptind, 0, iter ? 0 : 1)) return special_builtin_error(Exit_ERROR); if (iter) { /* break/continue iteration */ if (!execstate.iterating) { xerror(0, Ngt("not in an iteration")); return Exit_ERROR; } if (wcscmp(ARGV(0), L"break") == 0) { exception = E_BREAK_ITERATION; } else { assert(wcscmp(ARGV(0), L"continue") == 0); exception = E_CONTINUE_ITERATION; } return laststatus; } else { unsigned count; const wchar_t *countstr = ARGV(xoptind); if (countstr == NULL) { count = 1; } else { unsigned long countl; if (!xwcstoul(countstr, 0, &countl)) { xerror(0, Ngt("`%ls' is not a valid integer"), countstr); return special_builtin_error(Exit_ERROR); } else if (countl == 0) { xerror(0, Ngt("%u is not a positive integer"), 0u); return special_builtin_error(Exit_ERROR); } else if (countl > UINT_MAX) { count = UINT_MAX; } else { count = (unsigned) countl; } } assert(count > 0); if (execstate.loopnest == 0) { xerror(0, Ngt("not in a loop")); return special_builtin_error(Exit_ERROR); } if (count > execstate.loopnest) count = execstate.loopnest; if (wcscmp(ARGV(0), L"break") == 0) { execstate.breakloopnest = execstate.loopnest - count; } else { assert(wcscmp(ARGV(0), L"continue") == 0); execstate.breakloopnest = execstate.loopnest - count + 1; exception = E_CONTINUE; } return Exit_SUCCESS; } } #if YASH_ENABLE_HELP const char break_help[] = Ngt( "exit a loop" ); const char break_syntax[] = Ngt( "\tbreak [count]\n" "\tbreak -i\n" ); const char continue_help[] = Ngt( "continue a loop" ); const char continue_syntax[] = Ngt( "\tcontinue [count]\n" "\tcontinue -i\n" ); #endif /* The "eval" built-in, which accepts the following option: * -i: iterative execution */ int eval_builtin(int argc __attribute__((unused)), void **argv) { bool iter = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, iter_options, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { case L'i': iter = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (iter) { return exec_iteration(&argv[xoptind], "eval"); } else { wchar_t *args = joinwcsarray(&argv[xoptind], L" "); exec_wcs(args, "eval", false); free(args); return laststatus; } } #if YASH_ENABLE_HELP const char eval_help[] = Ngt( "evaluate arguments as a command" ); const char eval_syntax[] = Ngt( "\teval [-i] [argument...]\n" ); #endif /* Options for the "." built-in. */ const struct xgetopt_T dot_options[] = { { L'A', L"no-alias", OPTARG_NONE, false, NULL, }, { L'L', L"autoload", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "." built-in, which accepts the following option: * -A: disable aliases * -L: autoload */ int dot_builtin(int argc, void **argv) { bool enable_alias = true, autoload = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, dot_options, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { case L'A': enable_alias = false; break; case L'L': autoload = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } const wchar_t *filename = ARGV(xoptind++); if (filename == NULL) return special_builtin_error(insufficient_operands_error(1)); bool has_args = xoptind < argc; if (has_args && posixly_correct) return special_builtin_error(too_many_operands_error(1)); char *mbsfilename = malloc_wcstombs(filename); if (mbsfilename == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return Exit_ERROR; } char *path; if (autoload) { path = which(mbsfilename, get_path_array(PA_LOADPATH), is_readable_regular); if (path == NULL) { xerror(0, Ngt("file `%s' was not found in $YASH_LOADPATH"), mbsfilename); goto error; } } else if (wcschr(filename, L'/') == NULL) { path = which(mbsfilename, get_path_array(PA_PATH), is_readable_regular); if (path == NULL) { if (!posixly_correct) { path = mbsfilename; } else { xerror(0, Ngt("file `%s' was not found in $PATH"), mbsfilename); goto error; } } } else { path = mbsfilename; } int fd = move_to_shellfd(open(path, O_RDONLY)); if (path != mbsfilename) free(path); if (fd < 0) { xerror(errno, Ngt("cannot open file `%s'"), mbsfilename); goto error; } if (has_args) { open_new_environment(false); set_positional_parameters(&argv[xoptind]); } execstate_T *saveexecstate = save_execstate(); reset_execstate(false); bool saveser = suppresserrreturn; suppresserrreturn = false; exec_input(fd, mbsfilename, enable_alias ? XIO_SUBST_ALIAS : 0); cancel_return(); suppresserrreturn = saveser; restore_execstate(saveexecstate); remove_shellfd(fd); xclose(fd); free(mbsfilename); if (has_args) { close_current_environment(); } return laststatus; error: free(mbsfilename); if (special_builtin_executed && !is_interactive_now) exit_shell_with_status(Exit_FAILURE); return Exit_FAILURE; } #if YASH_ENABLE_HELP const char dot_help[] = Ngt( "read a file and execute commands" ); const char dot_syntax[] = Ngt( "\t. [-AL] file [argument...]\n" ); #endif /* Options for the "exec" built-in. */ const struct xgetopt_T exec_options[] = { { L'a', L"as", OPTARG_REQUIRED, false, NULL, }, { L'c', L"clear", OPTARG_NONE, false, NULL, }, { L'f', L"force", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "exec" built-in, which accepts the following options: * -a name: give as argv[0] to the command * -c: don't pass environment variables to the command * -f: suppress error when we have stopped jobs */ int exec_builtin(int argc, void **argv) { const wchar_t *as = NULL; bool clear = false, force = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, exec_options, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { case L'a': as = xoptarg; break; case L'c': clear = true; break; case L'f': force = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } exec_builtin_executed = true; if (xoptind == argc) return Exit_SUCCESS; if (!posixly_correct && is_interactive_now && !force) { size_t sjc = stopped_job_count(); if (sjc > 0) { fprintf(stderr, ngt("You have a stopped job!", "You have %zu stopped jobs!", sjc), sjc); fprintf(stderr, gt(" Use the -f option to exec anyway.\n")); return Exit_FAILURE; } } return exec_builtin_2(argc - xoptind, &argv[xoptind], as, clear); } /* The main part of the "exec" built-in. * argc, argv: the operands (not arguments) of the "exec" built-in. * as: value of the -a option or NULL * clear: true iff the -c option is specified */ int exec_builtin_2(int argc, void **argv, const wchar_t *as, bool clear) { int err; const wchar_t *saveargv0 = ARGV(0); char *mbssaveargv0 = malloc_wcstombs(saveargv0); if (mbssaveargv0 == NULL) { xerror(EILSEQ, Ngt("unexpected error")); err = Exit_NOEXEC; goto error1; } char *mbsargv0; if (as != NULL) { mbsargv0 = malloc_wcstombs(as); if (mbsargv0 == NULL) { xerror(EILSEQ, Ngt("unexpected error")); err = Exit_NOEXEC; goto error2; } argv[0] = (void *) as; } else { mbsargv0 = mbssaveargv0; } const char *commandpath; if (wcschr(saveargv0, L'/') != NULL) { commandpath = mbssaveargv0; } else { commandpath = get_command_path(mbssaveargv0, false); if (commandpath == NULL) { xerror(0, Ngt("no such command `%s'"), mbssaveargv0); err = Exit_NOTFOUND; goto error3; } } char **envs; if (clear) { /* use the environment that contains only the variables assigned by the * assignment for this `exec' built-in. */ plist_T list; pl_init(&list); for (const assign_T *assign = last_assign; assign != NULL; assign = assign->next) { char *value = get_exported_value(assign->a_name); if (value != NULL) { pl_add(&list, malloc_printf("%ls=%s", assign->a_name, value)); free(value); } } envs = (char **) pl_toary(&list); } else { envs = environ; } exec_external_program(commandpath, argc, mbsargv0, argv, envs); err = laststatus; if (clear) plfree((void **) envs, free); error3: if (as != NULL) { free(mbsargv0); argv[0] = (void *) saveargv0; } error2: free(mbssaveargv0); error1: if (posixly_correct || !is_interactive_now) exit(err); return err; } #if YASH_ENABLE_HELP const char exec_help[] = Ngt( "replace the shell process with an external command" ); const char exec_syntax[] = Ngt( "\texec [-cf] [-a name] [command [argument...]]\n" ); #endif /* Options for the "command" built-in. */ const struct xgetopt_T command_options[] = { { L'a', L"alias", OPTARG_NONE, false, NULL, }, { L'b', L"builtin-command", OPTARG_NONE, false, NULL, }, { L'e', L"external-command", OPTARG_NONE, false, NULL, }, { L'f', L"function", OPTARG_NONE, false, NULL, }, { L'k', L"keyword", OPTARG_NONE, false, NULL, }, { L'p', L"standard-path", OPTARG_NONE, true, NULL, }, { L'v', L"identify", OPTARG_NONE, true, NULL, }, { L'V', L"verbose-identify", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "command"/"type" built-in, which accepts the following options: * -a: search aliases * -b: search built-ins * -e: search external commands * -f: search functions * -k: search keywords * -p: use the default path to find the command * -v: print info about the command * -V: print info about the command in a human-friendly format */ int command_builtin(int argc, void **argv) { bool argv0istype = wcscmp(ARGV(0), L"type") == 0; bool printinfo = argv0istype, humanfriendly = argv0istype; enum srchcmdtype_T type = 0; bool aliases = false, keywords = false, defpath = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, command_options, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { case L'a': aliases = true; break; case L'b': type |= SCT_BUILTIN; break; case L'e': type |= SCT_EXTERNAL; break; case L'f': type |= SCT_FUNCTION; break; case L'k': keywords = true; break; case L'p': defpath = true; break; case L'v': printinfo = true; humanfriendly = false; break; case L'V': printinfo = true; humanfriendly = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (!printinfo) { if (aliases || keywords) { xerror(0, Ngt("the -a or -k option must be used with the -v option")); return Exit_ERROR; } if (xoptind == argc) { if (posixly_correct) return insufficient_operands_error(1); else return Exit_SUCCESS; } if (type == 0) type = SCT_EXTERNAL | SCT_BUILTIN; else type |= SCT_ALL; if (defpath) type |= SCT_STDPATH; return command_builtin_execute( argc - xoptind, &argv[xoptind], type); } else { if (posixly_correct && !validate_operand_count(argc - xoptind, 1, argv0istype ? SIZE_MAX : 1)) return Exit_ERROR; if (type == 0 && !aliases && !keywords) { type = SCT_EXTERNAL | SCT_BUILTIN | SCT_FUNCTION; aliases = keywords = true; } else { type |= SCT_ALL; } if (defpath) type |= SCT_STDPATH; type |= SCT_CHECK; bool ok = true; clearerr(stdout); for (int i = xoptind; i < argc; i++) { ok &= print_command_info( ARGV(i), type, aliases, keywords, humanfriendly); if (ferror(stdout)) break; } return ok && yash_error_message_count == 0 ? Exit_SUCCESS : Exit_FAILURE; } } /* Executes the specified simple command. * `argc' must be positive. * Returns the exit status of the command. */ int command_builtin_execute(int argc, void **argv, enum srchcmdtype_T type) { char *argv0 = malloc_wcstombs(argv[0]); commandinfo_T ci; bool finally_exit = false; if (argv0 == NULL) { xerror(EILSEQ, NULL); return Exit_NOTFOUND; } search_command(argv0, argv[0], &ci, type); if (ci.type == CT_EXTERNALPROGRAM) { pid_t cpid = fork_and_reset(0, true, t_leave); if (cpid < 0) { free(argv0); return Exit_NOEXEC; } else if (cpid > 0) { wchar_t **namep = wait_for_child( cpid, doing_job_control_now ? cpid : 0, doing_job_control_now); if (namep != NULL) *namep = joinwcsarray(argv, L" "); free(argv0); return laststatus; } finally_exit = true; } exec_simple_command(&ci, argc, argv0, argv, finally_exit); free(argv0); return laststatus; } /* Prints info about the specified command. * If the command is not found, returns false. */ bool print_command_info( const wchar_t *commandname, enum srchcmdtype_T type, bool aliases, bool keywords, bool humanfriendly) { const char *msgfmt; char *name = NULL; if (keywords && is_keyword(commandname)) { msgfmt = humanfriendly ? gt("%ls: a shell keyword\n") : "%ls\n"; xprintf(msgfmt, commandname); return true; } if (aliases) { if (print_alias_if_defined(commandname, humanfriendly)) { return true; } else { if (ferror(stdout)) return false; } } name = malloc_wcstombs(commandname); if (name == NULL) return false; commandinfo_T ci; search_command(name, commandname, &ci, type); switch (ci.type) { case CT_NONE: if (humanfriendly) xerror(0, Ngt("no such command `%s'"), name); break; case CT_EXTERNALPROGRAM: print_command_absolute_path(name, ci.ci_path, humanfriendly); break; case CT_SPECIALBUILTIN: msgfmt = humanfriendly ? gt("%s: a special built-in\n") : "%s\n"; xprintf(msgfmt, name); break; case CT_SEMISPECIALBUILTIN: msgfmt = humanfriendly ? gt("%s: a semi-special built-in\n") : "%s\n"; xprintf(msgfmt, name); break; case CT_REGULARBUILTIN:; const char *cmdpath; if (type & SCT_STDPATH) cmdpath = get_command_path_default(name); else cmdpath = get_command_path(name, false); if (humanfriendly) { msgfmt = (cmdpath == NULL) ? Ngt("%s: a regular built-in (not found in $PATH)\n") : Ngt("%s: a regular built-in at %s\n"); xprintf(gt(msgfmt), name, cmdpath); } else { xprintf("%s\n", (cmdpath == NULL) ? name : cmdpath); } break; case CT_FUNCTION: msgfmt = humanfriendly ? gt("%s: a function\n") : "%s\n"; xprintf(msgfmt, name); break; } free(name); return ci.type != CT_NONE; } /* Prints the absolute path of the specified command. */ void print_command_absolute_path( const char *name, const char *path, bool humanfriendly) { if (path[0] == '/') { /* the path is already absolute */ if (humanfriendly) xprintf(gt("%s: an external command at %s\n"), name, path); else xprintf("%s\n", path); return; } const wchar_t *wpwd = getvar(L VAR_PWD); char *pwd = NULL; if (wpwd != NULL) { pwd = malloc_wcstombs(wpwd); if (pwd != NULL && !is_same_file(pwd, ".")) { free(pwd); pwd = NULL; } } if (pwd == NULL) { pwd = xgetcwd(); if (pwd == NULL) pwd = xstrdup("."); /* last resort */ } if (humanfriendly) xprintf(gt("%s: an external command at %s/%s\n"), name, pwd, path); else xprintf("%s/%s\n", pwd, path); free(pwd); } #if YASH_ENABLE_HELP const char command_help[] = Ngt( "execute or identify a command" ); const char command_syntax[] = Ngt( "\tcommand [-befp] command [argument...]\n" "\tcommand -v|-V [-abefkp] command...\n" ); const char type_help[] = Ngt( "identify a command" ); const char type_syntax[] = Ngt( "\ttype command...\n" ); #endif /* The "times" built-in. */ int times_builtin(int argc __attribute__((unused)), void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, 0)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (xoptind < argc) return special_builtin_error(too_many_operands_error(0)); double clock; struct tms tms; intmax_t sum, ssm, cum, csm; double sus, sss, cus, css; #define format_time(time, min, sec) \ do { \ double tsec = (time) / clock; \ double m = trunc(tsec / 60.0); \ (min) = (intmax_t) m; \ (sec) = tsec - m * 60.0; \ } while (0) clock = sysconf(_SC_CLK_TCK); if (times(&tms) == (clock_t) -1) { xerror(errno, Ngt("cannot get the time data")); return special_builtin_error(Exit_FAILURE); } format_time(tms.tms_utime, sum, sus); format_time(tms.tms_stime, ssm, sss); format_time(tms.tms_cutime, cum, cus); format_time(tms.tms_cstime, csm, css); #undef format_time xprintf("%jdm%fs %jdm%fs\n%jdm%fs %jdm%fs\n", sum, sus, ssm, sss, cum, cus, csm, css); return (yash_error_message_count == 0) ? Exit_SUCCESS : special_builtin_error(Exit_FAILURE); } #if YASH_ENABLE_HELP const char times_help[] = Ngt( "print CPU time usage" ); const char times_syntax[] = Ngt( "\ttimes\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/exec.h000066400000000000000000000101641354143602500135160ustar00rootroot00000000000000/* Yash: yet another shell */ /* exec.h: command execution */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_EXEC_H #define YASH_EXEC_H #include #include #include "xgetopt.h" /* options for `fork_and_reset' */ typedef enum sigtype_T { t_quitint = 1 << 0, t_tstp = 1 << 1, t_leave = 1 << 2, } sigtype_T; #define Exit_SUCCESS 0 #define Exit_FAILURE 1 #define Exit_ERROR 2 #define Exit_NOEXEC 126 #define Exit_NOTFOUND 127 #define Exit_SYNERROR (256 + Exit_ERROR) #define Exit_EXPERROR Exit_ERROR #define Exit_ASSGNERR Exit_ERROR #define Exit_REDIRERR Exit_ERROR extern int laststatus, savelaststatus, exitstatus; extern pid_t lastasyncpid; extern _Bool special_builtin_executed; extern _Bool is_executing_auxiliary; struct execstate_T; extern void reset_execstate(_Bool reset_iteration); extern struct execstate_T *save_execstate(void) __attribute__((malloc,warn_unused_result)); extern void restore_execstate(struct execstate_T *save) __attribute__((nonnull)); extern void disable_return(void); extern void cancel_return(void); extern _Bool need_break(void) __attribute__((pure)); struct and_or_T; struct embedcmd_T; extern void exec_and_or_lists(const struct and_or_T *a, _Bool finally_exit); extern pid_t fork_and_reset(pid_t pgid, _Bool fg, sigtype_T sigtype); extern struct xwcsbuf_T *get_xtrace_buffer(void); extern wchar_t *exec_command_substitution(const struct embedcmd_T *cmdsub) __attribute__((nonnull,malloc,warn_unused_result)); extern int exec_variable_as_commands( const wchar_t *varname, const char *codename) __attribute__((nonnull)); extern int exec_variable_as_auxiliary( const wchar_t *varname, const char *codename) __attribute__((nonnull)); #define exec_variable_as_auxiliary_(varname) \ exec_variable_as_auxiliary(L varname, "$" varname) #if YASH_ENABLE_LINEEDIT extern _Bool autoload_completion_function_file( const wchar_t *filename, const wchar_t *cmdname) __attribute__((nonnull(1))); extern _Bool call_completion_function(const wchar_t *funcname) __attribute__((nonnull)); #endif extern const struct xgetopt_T iter_options[]; extern int return_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char return_help[], return_syntax[]; #endif extern const struct xgetopt_T return_options[]; extern int break_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char break_help[], break_syntax[], continue_help[], continue_syntax[]; #endif extern int eval_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char eval_help[], eval_syntax[]; #endif extern int dot_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char dot_help[], dot_syntax[]; #endif extern const struct xgetopt_T dot_options[]; extern int exec_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char exec_help[], exec_syntax[]; #endif extern const struct xgetopt_T exec_options[]; extern int command_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char command_help[], command_syntax[], type_help[], type_syntax[]; #endif extern const struct xgetopt_T command_options[]; extern int times_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char times_help[], times_syntax[]; #endif #endif /* YASH_EXEC_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/expand.c000066400000000000000000001562771354143602500140640ustar00rootroot00000000000000/* Yash: yet another shell */ /* expand.c: word expansion */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "expand.h" #include #include #include #include #include #include #include #include #include #include #include "arith.h" #include "exec.h" #include "input.h" #include "option.h" #include "parser.h" #include "path.h" #include "plist.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "xfnmatch.h" #include "yash.h" /* characters that have special meanings in brace expansion, quote removal, and * globbing. When an unquoted expansion includes these characters, they are * backslashed to protect from unexpected side effects in succeeding expansion * steps. */ #define CHARS_ESCAPED L"\\\"\'{,}" static bool expand_and_split_words( const wordunit_T *restrict w, plist_T *restrict list) __attribute__((nonnull(2))); /* data passed between expansion functions */ struct expand_four_T { plist_T *valuelist, *splitlist; xwcsbuf_T valuebuf; xstrbuf_T splitbuf; bool zeroword; }; /* When "$@" appears during expansion and there is no positional parameter, the * `zeroword' flag is set so that the quoted empty word can be removed later. */ static bool expand_four_and_remove_quotes( const wordunit_T *restrict w, tildetype_T tilde, bool processquotes, bool escapeall, plist_T *restrict valuelist) __attribute__((nonnull(5))); static bool expand_four(const wordunit_T *restrict w, tildetype_T tilde, bool processquotes, bool escapeall, bool rec, struct expand_four_T *restrict e) __attribute__((nonnull(6))); static void fill_splitbuf(struct expand_four_T *e, bool splittable) __attribute__((nonnull)); static wchar_t *expand_tilde(const wchar_t **ss, bool hasnextwordunit, tildetype_T tt) __attribute__((nonnull,malloc,warn_unused_result)); enum indextype_T { IDX_NONE, IDX_ALL, IDX_CONCAT, IDX_NUMBER, }; static bool expand_param(const paramexp_T *restrict p, bool indq, struct expand_four_T *restrict e) __attribute__((nonnull)); static enum indextype_T parse_indextype(const wchar_t *indexstr) __attribute__((nonnull,pure)); static wchar_t *trim_wstring(wchar_t *s, ssize_t startindex, ssize_t endindex) __attribute__((nonnull)); static void **trim_array(void **a, ssize_t startindex, ssize_t endindex) __attribute__((nonnull)); static void print_subst_as_error(const paramexp_T *p) __attribute__((nonnull)); static void match_each(void **restrict slist, const wchar_t *restrict pattern, paramexptype_T type) __attribute__((nonnull)); static void subst_each(void **restrict slist, const wchar_t *pattern, const wchar_t *subst, paramexptype_T type) __attribute__((nonnull)); static wchar_t *concatenate_values(void **values, bool escape) __attribute__((nonnull,malloc,warn_unused_result)); static void **concatenate_values_into_array(void **values, bool escape) __attribute__((nonnull,malloc,warn_unused_result)); static void subst_length_each(void **slist) __attribute__((nonnull)); static void expand_brace_each(void **restrict values, void **restrict splits, plist_T *restrict valuelist, plist_T *restrict splitlist) __attribute__((nonnull)); static void expand_brace(wchar_t *restrict word, char *restrict split, plist_T *restrict valuelist, plist_T *restrict splitlist) __attribute__((nonnull)); static bool try_expand_brace_sequence( wchar_t *word, char *restrict split, wchar_t *startc, plist_T *restrict valuelist, plist_T *restrict splitlist) __attribute__((nonnull)); static bool has_leading_zero(const wchar_t *restrict s, bool *restrict sign) __attribute__((nonnull)); static void fieldsplit_all(void **restrict valuelist, void **restrict splitlist, plist_T *restrict dest) __attribute__((nonnull)); static void fieldsplit(wchar_t *restrict s, char *restrict split, const wchar_t *restrict ifs, plist_T *restrict dest) __attribute__((nonnull)); static size_t skip_ifs(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) __attribute__((nonnull,pure)); static size_t skip_ifs_whitespaces(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) __attribute__((nonnull,pure)); static size_t skip_field(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) __attribute__((nonnull,pure)); static void add_empty_field(plist_T *dest, const wchar_t *p) __attribute__((nonnull)); static inline void add_sq( const wchar_t *restrict *ss, xwcsbuf_T *restrict buf, bool escape) __attribute__((nonnull)); static wchar_t *escaped_wcspbrk(const wchar_t *s, const wchar_t *accept) __attribute__((nonnull)); static wchar_t *escaped_remove(const wchar_t *s, const wchar_t *reject) __attribute__((nonnull,malloc,warn_unused_result)); static inline wchar_t *escaped_remove_free(wchar_t *s, const wchar_t *reject) __attribute__((nonnull,malloc,warn_unused_result)); static void glob_all(void **restrict patterns, plist_T *restrict list) __attribute__((nonnull)); static enum wglobflags_T get_wglobflags(void) __attribute__((pure)); static void maybe_exit_on_error(void); /********** Entry Points **********/ /* Expands a command line. * `args' is a NULL-terminated array of pointers to `const wordunit_T' * to expand. * If successful, the number of resulting words is assigned to `*argcp', a * pointer to a newly malloced array of the expanded words is assigned to * `*argvp', and true is returned. The array is NULL-terminated and its elements * are newly malloced wide strings. * If unsuccessful, false is returned and the values of `*argcp' and `*argvp' * are indeterminate. * On error in a non-interactive shell, the shell exits. */ bool expand_line(void *const *restrict args, int *restrict argcp, void ***restrict argvp) { plist_T list; pl_init(&list); for (; *args != NULL; args++) { if (!expand_multiple(*args, &list)) { plfree(pl_toary(&list), free); return false; } } *argcp = list.length; *argvp = pl_toary(&list); return true; } /* Expands a word. * The results, which are added to `list' as newly-malloced wide strings, may * be multiple words. * The return value is true iff successful. * On error in a non-interactive shell, the shell exits. */ bool expand_multiple(const wordunit_T *w, plist_T *list) { plist_T templist; /* four expansions, brace expansions and field splitting */ if (!expand_and_split_words(w, pl_init(&templist))) { maybe_exit_on_error(); plfree(pl_toary(&templist), free); return false; } /* glob */ if (shopt_glob) { glob_all(pl_toary(&templist), list); } else { for (size_t i = 0; i < templist.length; i++) pl_add(list, unescapefree(templist.contents[i])); pl_destroy(&templist); } return true; } /* Performs the four expansions, brace expansion and field splitting in a word. * The four expansions are tilde expansion, parameter expansion, command * substitution and arithmetic expansion. * Returns true iff successful. The resulting words are added to `list', which * may include backslash escapes. * Tilde expansion is performed with TT_SINGLE. */ bool expand_and_split_words( const wordunit_T *restrict w, plist_T *restrict list) { plist_T valuelist1, valuelist2, splitlist1, splitlist2; pl_init(&valuelist1); pl_init(&splitlist1); struct expand_four_T expand; expand.valuelist = &valuelist1; expand.splitlist = &splitlist1; wb_init(&expand.valuebuf); sb_init(&expand.splitbuf); expand.zeroword = false; /* four expansions (w -> list1) */ if (!expand_four(w, TT_SINGLE, true, false, false, &expand)) { plfree(pl_toary(&valuelist1), free); plfree(pl_toary(&splitlist1), free); wb_destroy(&expand.valuebuf); sb_destroy(&expand.splitbuf); return false; } assert(expand.valuebuf.length == expand.splitbuf.length); pl_add(expand.valuelist, wb_towcs(&expand.valuebuf)); pl_add(expand.splitlist, sb_tostr(&expand.splitbuf)); /* brace expansion (list1 -> list2) */ if (shopt_braceexpand) { pl_init(&valuelist2); pl_init(&splitlist2); expand_brace_each(valuelist1.contents, splitlist1.contents, &valuelist2, &splitlist2); pl_destroy(&valuelist1); pl_destroy(&splitlist1); } else { valuelist2 = valuelist1; splitlist2 = splitlist1; } /* field splitting (list2 -> list) */ size_t oldlength = list->length; fieldsplit_all(pl_toary(&valuelist2), pl_toary(&splitlist2), list); assert(oldlength <= list->length); /* empty field removal */ if (list->length - oldlength == 1) { wchar_t *field = list->contents[oldlength]; if (field[0] == L'\0' || (expand.zeroword && wcscmp(field, L"\"\"") == 0)) { free(field); pl_remove(list, oldlength, 1); } } /* quote removal */ for (size_t i = oldlength; i < list->length; i++) list->contents[i] = escaped_remove_free(list->contents[i], L"\"\'"); return true; } /* Expands a single word: the four expansions and quote removal. * This function doesn't perform brace expansion, field splitting, globbing and * unescaping. * If `processquotes' is true, single- and double-quotations are recognized as * quotes. Otherwise, they are treated like backslashed characters. * If `escapeall' is true, the expanded words are all backslashed as if the * entire expansion is quoted. * If `processquotes' and `escapeall' are false, only backslashes not preceding * any of $, `, \ are self-backslashed. * If successful, the resulting word is returned as a newly malloced string * that may include backslash escapes. * On error, an error message is printed and NULL is returned. * On error in a non-interactive shell, the shell exits. */ wchar_t *expand_single(const wordunit_T *arg, tildetype_T tilde, bool processquotes, bool escapeall) { plist_T list; pl_init(&list); if (!expand_four_and_remove_quotes( arg, tilde, processquotes, escapeall, &list)) { maybe_exit_on_error(); plfree(pl_toary(&list), free); return NULL; } return concatenate_values(pl_toary(&list), true); } /* Like `expand_single', but the result is unescaped (if successful). */ wchar_t *expand_single_and_unescape(const wordunit_T *arg, tildetype_T tilde, bool processquotes, bool escapeall) { wchar_t *result = expand_single(arg, tilde, processquotes, escapeall); return result == NULL ? NULL : unescapefree(result); } /* Expands a single word: the four expansions, glob, quote removal and unescape. * This function doesn't perform brace expansion and field splitting. * If the result of glob is more than one word, * - returns the pre-glob pattern string if in the POSIXly correct mode * - treats as an error otherwise. * If the "glob" shell option is off, glob is not performed. * The "nullglob" shell option is ignored. * If successful, the resulting word is returned as a newly malloced string. * On error, an error message is printed and NULL is returned. * On error in a non-interactive shell, the shell exits. */ char *expand_single_with_glob(const wordunit_T *arg, tildetype_T tilde) { wchar_t *exp = expand_single(arg, tilde, true, false); char *result; if (exp == NULL) return NULL; /* glob */ if (shopt_glob && is_pathname_matching_pattern(exp)) { plist_T list; bool ok; pl_init(&list); set_interruptible_by_sigint(true); ok = wglob(exp, get_wglobflags(), &list); set_interruptible_by_sigint(false); if (!ok) { free(exp); plfree(pl_toary(&list), free); xerror(EINTR, Ngt("redirection")); result = NULL; } else if (list.length == 1) { free(exp); result = realloc_wcstombs(list.contents[0]); if (result == NULL) xerror(EILSEQ, Ngt("redirection")); pl_destroy(&list); } else { plfree(pl_toary(&list), free); if (posixly_correct) { goto noglob; } else { exp = unescapefree(exp); xerror(0, Ngt("filename `%ls' matches more than one file"), exp); free(exp); result = NULL; } } } else { noglob: result = realloc_wcstombs(unescapefree(exp)); if (result == NULL) xerror(EILSEQ, Ngt("redirection")); } return result; } /********** Four Expansions **********/ /* Performs the four expansions in the specified single word. * `w' is the word in which expansions occur. * `tilde' is type of tilde expansion that is performed. * If `processquotes' is true, single- and double-quotations are recognized as * quotes. Otherwise, they are treated like backslashed characters. * If `escapeall' is true, the expanded words are all backslashed as if the * entire expansion is quoted. * If `processquotes' and `escapeall' are false, only backslashes not preceding * any of $, `, \ are self-backslashed. * The expanded word is added to `valuelist' as a newly malloced wide string. * Single- or double-quoted characters are unquoted and backslashed. * In most cases, one string is added to `valuelist'. If the word contains "$@", * however, any number of strings may be added. * The return value is true iff successful. */ bool expand_four_and_remove_quotes( const wordunit_T *restrict w, tildetype_T tilde, bool processquotes, bool escapeall, plist_T *restrict valuelist) { size_t oldlength = valuelist->length; struct expand_four_T expand; expand.valuelist = valuelist; wb_init(&expand.valuebuf); expand.splitlist = NULL; expand.zeroword = false; bool ok = expand_four(w, tilde, processquotes, escapeall, false, &expand); /* remove empty word for "$@" if $# == 0 */ if (valuelist->length == oldlength && expand.zeroword && wcscmp(expand.valuebuf.contents, L"\"\"") == 0) wb_destroy(&expand.valuebuf); else pl_add(valuelist, wb_towcs(&expand.valuebuf)); /* quote removal */ for (size_t i = oldlength; i < valuelist->length; i++) valuelist->contents[i] = escaped_remove_free(valuelist->contents[i], L"\"\'"); return ok; } /* Performs the four expansions in the specified single word. * The four expansions are tilde expansion, parameter expansion, command * substitution, and arithmetic expansion. * `w' is the word in which expansions occur. * `tilde' specifies the type of tilde expansion that is performed. * If `processquotes' is true, single- and double-quotations are recognized as * quotes. Otherwise, they are treated like backslashed characters. * If `escapeall' is true, the expanded words are all backslashed as if the * entire expansion is quoted. * If `processquotes' and `escapeall' are false, only backslashes not preceding * any of $, `, \ are self-backslashed. * `rec' must be true iff this expansion is part of another expansion. * `e->valuebuf' must be initialized before calling this function and is used to * expand the current word. If `w' expands to multiple words, the last word is * put in `e->valuebuf' and the others are inserted to `e->valuelist'. * The splittability strings are put in `e->splitbuf' and `e->splitlist' * accordingly if `e->splitlist' is non-NULL. * Single- and double-quotations remain in the resulting word. In addition, * characters inside those quotations are backslashed. * The return value is true iff successful. */ /* A splittability string is an array of Boolean values that specifies where * the word can be split in field splitting. The word can be split at the nth * character iff the nth value of the splittability string is non-zero. */ bool expand_four(const wordunit_T *restrict w, tildetype_T tilde, bool processquotes, bool escapeall, bool rec, struct expand_four_T *restrict e) { bool ok = true; bool indq = false; /* in a double quote? */ bool first = true; /* is the first word unit? */ const wchar_t *ss; wchar_t *s; #define FILL_SBUF(s) fill_splitbuf(e, !indq && !escapeall && (s)); #define FILL_SBUF_SPLITTABLE FILL_SBUF(true) #define FILL_SBUF_UNSPLITTABLE FILL_SBUF(false) for (; w != NULL; w = w->next, first = false) { switch (w->wu_type) { case WT_STRING: ss = w->wu_string; if (first && tilde != TT_NONE) { s = expand_tilde(&ss, w->next, tilde); if (s != NULL) { wb_catfree(&e->valuebuf, escapefree(s, NULL)); FILL_SBUF_UNSPLITTABLE; } } while (*ss != L'\0') { switch (*ss) { case L'"': if (!processquotes) goto escape; indq = !indq; wb_wccat(&e->valuebuf, L'"'); FILL_SBUF_UNSPLITTABLE; break; case L'\'': if (!processquotes || indq) goto escape; wb_wccat(&e->valuebuf, L'\''); add_sq(&ss, &e->valuebuf, true); wb_wccat(&e->valuebuf, L'\''); FILL_SBUF_UNSPLITTABLE; break; case L'\\': if (!processquotes) { if (!escapeall) { wchar_t c = ss[1]; if (c == L'$' || c == L'`' || c == L'\\') ss++; } goto escape; } if (indq && wcschr(CHARS_ESCAPABLE, ss[1]) == NULL) { goto escape; } else { wb_wccat(&e->valuebuf, L'\\'); if (*++ss != L'\0') wb_wccat(&e->valuebuf, *ss++); FILL_SBUF_UNSPLITTABLE; continue; } case L':': if (!indq && tilde == TT_MULTI) { /* perform tilde expansion after a colon */ wb_wccat(&e->valuebuf, L':'); ss++; s = expand_tilde(&ss, w->next, tilde); if (s != NULL) { wb_catfree(&e->valuebuf, escapefree(s, NULL)); FILL_SBUF_UNSPLITTABLE; } continue; } /* falls thru! */ default: if (indq || escapeall) escape: wb_wccat(&e->valuebuf, L'\\'); wb_wccat(&e->valuebuf, *ss); FILL_SBUF(rec); break; } ss++; } break; case WT_PARAM: if (!expand_param(w->wu_param, indq || escapeall, e)) ok = false; break; case WT_CMDSUB: s = exec_command_substitution(&w->wu_cmdsub); goto cat_s; case WT_ARITH: s = expand_single_and_unescape(w->wu_arith, TT_NONE, true, false); if (s != NULL) s = evaluate_arithmetic(s); cat_s: if (s != NULL) { wb_catfree(&e->valuebuf, escapefree(s, (indq || escapeall) ? NULL : CHARS_ESCAPED)); FILL_SBUF_SPLITTABLE; } else { ok = false; } break; } } #undef FILL_SBUF_UNSPLITTABLE #undef FILL_SBUF_SPLITTABLE #undef FILL_SBUF return ok; } /* Appends to `e->splitbuf' as many `splittable' as needed to match the length * with `e->valuebuf'. */ void fill_splitbuf(struct expand_four_T *e, bool splittable) { if (e->splitlist == NULL) return; sb_ccat_repeat( &e->splitbuf, splittable, e->valuebuf.length - e->splitbuf.length); } /* Performs tilde expansion. * `ss' is a pointer to a pointer to the tilde character. The pointer is * increased so that it points to the character right after the expanded string. * The string pointed by the pointer pointed by `ss' should be contents of a * word unit of type WT_STRING. Iff there is a next word unit, `hasnextwordunit' * must be true. * If `**ss' is not L'~' or expansion fails, this function has no side effects * and returns NULL. If successful, `*ss' is incremented and the result is * returned as a newly malloced string. */ wchar_t *expand_tilde(const wchar_t **ss, bool hasnextwordunit, tildetype_T tt) { const wchar_t *s = *ss; if (*s != L'~') return NULL; s++; const wchar_t *end = wcspbrk(s, tt == TT_SINGLE ? L"/" : L"/:"); wchar_t *username; const wchar_t *home; size_t usernamelen; if (end != NULL) { usernamelen = end - s; } else { if (hasnextwordunit) return NULL; usernamelen = wcslen(s); } username = xwcsndup(s, usernamelen); if (username[0] == L'\0') { /* empty user name: use $HOME */ home = getvar(L VAR_HOME); goto finish; } else if (wcspbrk(username, L"\"'\\") != 0) { /* don't expand if the user name is quoted */ free(username); return NULL; } if (!posixly_correct) { if (username[0] == L'+' && username[1] == L'\0') { home = getvar(L VAR_PWD); goto finish; } if (username[0] == L'-' && username[1] == L'\0') { home = getvar(L VAR_OLDPWD); goto finish; } #if YASH_ENABLE_DIRSTACK if (username[0] == L'+' || username[0] == L'-') { size_t index; if (parse_dirstack_index(username, &index, &home, false) && index != SIZE_MAX) { goto finish; } } #endif } home = get_home_directory(username, false); finish: free(username); if (home == NULL) return NULL; *ss = s + usernamelen; return xwcsdup(home); } /* Performs parameter expansion. * The result is put in `e'. * Returns true iff successful. */ bool expand_param(const paramexp_T *restrict p, bool indq, struct expand_four_T *restrict e) { /* parse indices first */ ssize_t startindex, endindex; enum indextype_T indextype; if (p->pe_start == NULL) { startindex = 0, endindex = SSIZE_MAX, indextype = IDX_NONE; } else { wchar_t *start = expand_single_and_unescape( p->pe_start, TT_NONE, true, false); if (start == NULL) return false; indextype = parse_indextype(start); if (indextype != IDX_NONE) { startindex = 0, endindex = SSIZE_MAX; free(start); if (p->pe_end != NULL) { xerror(0, Ngt("the parameter index is invalid")); return false; } } else if (!evaluate_index(start, &startindex)) { return false; } else { if (p->pe_end == NULL) { endindex = (startindex == -1) ? SSIZE_MAX : startindex; } else { wchar_t *end = expand_single_and_unescape( p->pe_end, TT_NONE, true, false); if (end == NULL || !evaluate_index(end, &endindex)) return false; } if (startindex == 0) startindex = SSIZE_MAX; else if (startindex >= 0) startindex--; } } /* Here, `startindex' and `endindex' are zero-based. `startindex' is * included in the range but `endindex' is not. A negative index will be * wrapped around the length. */ /* get the value of parameter or nested expansion */ struct get_variable_T v; bool unset; /* parameter is not set? */ if (p->pe_type & PT_NEST) { plist_T plist; pl_init(&plist); if (!expand_four_and_remove_quotes( p->pe_nest, TT_NONE, true, true, &plist)) { plfree(pl_toary(&plist), free); return false; } v.type = (plist.length == 1) ? GV_SCALAR : GV_ARRAY; v.count = plist.length; v.values = pl_toary(&plist); v.freevalues = true; unset = false; for (size_t i = 0; v.values[i] != NULL; i++) v.values[i] = unescapefree(v.values[i]); } else { v = get_variable(p->pe_name); if (v.type == GV_NOTFOUND) { /* if the variable is not set, return empty string */ v.type = GV_SCALAR; v.count = 1; v.values = xmallocn(2, sizeof *v.values); v.values[0] = xwcsdup(L""); v.values[1] = NULL; v.freevalues = true; unset = true; } else { unset = false; } } /* here, the contents of `v.values' are not escaped by backslashes. */ /* modify the elements of `v.values' according to the indices */ void **values; /* the result */ bool concat; /* concatenate array elements? */ switch (v.type) { case GV_SCALAR: assert(v.values != NULL && v.count == 1); save_get_variable_values(&v); if (indextype != IDX_NUMBER) { trim_wstring(v.values[0], startindex, endindex); } else { size_t len = wcslen(v.values[0]); free(v.values[0]); v.values[0] = malloc_wprintf(L"%zu", len); } values = v.values, concat = false; break; case GV_ARRAY: concat = false; goto treat_array; case GV_ARRAY_CONCAT: concat = true; treat_array: switch (indextype) { case IDX_CONCAT: concat = true; /* falls thru! */ case IDX_NONE: case IDX_ALL: if (startindex >= 0) { #if SIZE_MAX >= SSIZE_MAX if ((size_t) startindex > v.count) #else if (startindex > (ssize_t) v.count) #endif startindex = v.count; } else { startindex += v.count; if (startindex < 0) startindex = 0; } if (endindex < 0) endindex += v.count + 1; if (endindex < startindex) endindex = startindex; #if SSIZE_MAX > SIZE_MAX else if (endindex > (ssize_t) SIZE_MAX) endindex = SIZE_MAX; #endif assert(0 <= startindex && startindex <= endindex); values = v.freevalues ? trim_array(v.values, startindex, endindex) : plndup(v.values + startindex, endindex - startindex, copyaswcs); break; case IDX_NUMBER: if (v.freevalues) plfree(v.values, free); values = xmallocn(2, sizeof *values); values[0] = malloc_wprintf(L"%zu", v.count); values[1] = NULL; concat = false; break; default: assert(false); } break; default: assert(false); } /* if `PT_COLON' is true, empty string is treated as unset */ if (p->pe_type & PT_COLON) if (values[0] == NULL || (((wchar_t *) values[0])[0] == L'\0' && values[1] == NULL)) unset = true; /* PT_PLUS, PT_MINUS, PT_ASSIGN, PT_ERROR */ wchar_t *subst; switch (p->pe_type & PT_MASK) { case PT_PLUS: if (!unset) goto subst; unset = false; break; case PT_MINUS: if (unset) { subst: plfree(values, free); return expand_four(p->pe_subst, TT_SINGLE, true, indq, true, e); } break; case PT_ASSIGN: if (unset) { plfree(values, free); if (p->pe_type & PT_NEST) { xerror(0, Ngt("a nested parameter expansion cannot be assigned")); return false; } else if (!is_name(p->pe_name)) { xerror(0, Ngt("cannot assign to parameter `%ls' " "in parameter expansion"), p->pe_name); return false; } else if ((v.type == GV_ARRAY_CONCAT) || (v.type == GV_ARRAY && startindex + 1 != endindex)) { xerror(0, Ngt("the specified index does not support assignment " "in the parameter expansion of array `%ls'"), p->pe_name); return false; } subst = expand_single_and_unescape( p->pe_subst, TT_SINGLE, true, false); if (subst == NULL) return false; if (v.type != GV_ARRAY) { assert(v.type == GV_NOTFOUND || v.type == GV_SCALAR); if (!set_variable( p->pe_name, xwcsdup(subst), SCOPE_GLOBAL, false)) { free(subst); return false; } } else { assert(0 <= startindex && (size_t) startindex <= v.count); if (!set_array_element(p->pe_name, startindex, xwcsdup(subst))){ free(subst); return false; } } values = xmallocn(2, sizeof *values); values[0] = subst; values[1] = NULL; unset = false; } break; case PT_ERROR: if (unset) { plfree(values, free); print_subst_as_error(p); return false; } break; } if (unset && !shopt_unset) { plfree(values, free); xerror(0, Ngt("parameter `%ls' is not set"), p->pe_name); return false; } /* PT_MATCH, PT_SUBST */ wchar_t *match; switch (p->pe_type & PT_MASK) { case PT_MATCH: match = expand_single(p->pe_match, TT_SINGLE, true, false); if (match == NULL) { plfree(values, free); return false; } match_each(values, match, p->pe_type); free(match); break; case PT_SUBST: match = expand_single(p->pe_match, TT_SINGLE, true, false); subst = expand_single_and_unescape(p->pe_subst, TT_SINGLE, true, false); if (match == NULL || subst == NULL) { free(match); free(subst); plfree(values, free); return false; } subst_each(values, match, subst, p->pe_type); free(match); free(subst); break; } /* concatenate the elements of `values' */ if (concat && indq) values = concatenate_values_into_array(values, false); /* PT_NUMBER */ if (p->pe_type & PT_NUMBER) subst_length_each(values); /* backslash escape */ for (size_t i = 0; values[i] != NULL; i++) values[i] = escapefree(values[i], indq ? NULL : CHARS_ESCAPED); /* add the elements of `values' to `e->valuelist' */ if (values[0] == NULL) { if (indq) e->zeroword = true; } else { /* add the first element */ wb_catfree(&e->valuebuf, values[0]); fill_splitbuf(e, !indq); if (values[1] != NULL) { pl_add(e->valuelist, wb_towcs(&e->valuebuf)); if (e->splitlist != NULL) pl_add(e->splitlist, sb_tostr(&e->splitbuf)); /* add the remaining but last */ size_t i; for (i = 1; values[i + 1] != NULL; i++) { pl_add(e->valuelist, values[i]); if (e->splitlist != NULL) { size_t len = wcslen(values[i]); pl_add(e->splitlist, memset(xmalloc(len), !indq, len)); } } /* add the last element */ wb_initwith(&e->valuebuf, values[i]); if (e->splitlist != NULL) { sb_init(&e->splitbuf); fill_splitbuf(e, !indq); } } } free(values); return true; } /* Returns IDX_ALL, IDX_CONCAT, IDX_NUMBER if `indexstr' is L"@", L"*", * L"#" respectively. Otherwise returns IDX_NONE. */ enum indextype_T parse_indextype(const wchar_t *indexstr) { if (indexstr[0] != L'\0' && indexstr[1] == L'\0') { switch (indexstr[0]) { case L'@': return IDX_ALL; case L'*': return IDX_CONCAT; case L'#': return IDX_NUMBER; } } return IDX_NONE; } /* Trims some leading and trailing characters of the wide string. * Characters in the range [`startindex', `endindex') remain. * Returns the string `s'. */ wchar_t *trim_wstring(wchar_t *s, ssize_t startindex, ssize_t endindex) { if (startindex == 0 && endindex == SSIZE_MAX) return s; if (startindex < 0 || endindex < 0) { ssize_t len = wcslen(s); if (startindex < 0) { startindex += len; if (startindex < 0) startindex = 0; } if (endindex < 0) { endindex += len + 1; if (endindex <= startindex) goto return_empty; } } assert(startindex >= 0 && endindex >= 0); if (startindex >= endindex) goto return_empty; for (ssize_t i = 0; i < startindex; i++) if (s[i] == L'\0') goto return_empty; for (ssize_t i = 0; i < endindex - startindex; i++) if ((s[i] = s[startindex + i]) == L'\0') return s; s[endindex - startindex] = L'\0'; return s; return_empty: s[0] = L'\0'; return s; } /* Trims some leading and trailing elements of the NULL-terminated array of * pointers. * Elements in the range [`startindex', `endindex') remain. `startindex' must * not be negative and `endindex' must not be less than `startindex'. * Removed elements are freed. * Returns the array `a'. */ /* `startindex' and/or `endindex' may be >= the length of the array. */ void **trim_array(void **a, ssize_t startindex, ssize_t endindex) { assert(0 <= startindex && startindex <= endindex); if (startindex == 0 && endindex == SSIZE_MAX) return a; ssize_t len = endindex - startindex; for (ssize_t i = 0; i < startindex; i++) { if (a[i] == NULL) { a[0] = NULL; return a; } free(a[i]); } for (ssize_t i = 0; i < len; i++) if ((a[i] = a[startindex + i]) == NULL) return a; for (ssize_t i = endindex; a[i] != NULL; i++) free(a[i]); a[len] = NULL; return a; } /* Expands `p->pe_subst' and prints it as an error message. */ void print_subst_as_error(const paramexp_T *p) { if (p->pe_subst != NULL) { wchar_t *subst = expand_single_and_unescape( p->pe_subst, TT_SINGLE, true, false); if (subst != NULL) { if (p->pe_type & PT_NEST) xerror(0, "%ls", subst); else xerror(0, "%ls: %ls", p->pe_name, subst); free(subst); } } else { /* use the default error message */ if (p->pe_type & PT_NEST) xerror(0, Ngt("the parameter value is empty")); else xerror(0, (p->pe_type & PT_COLON) ? Ngt("parameter `%ls' is not set or has an empty value") : Ngt("parameter `%ls' is not set"), p->pe_name); } } /* Matches each string in array `slist' to pattern `pattern' and removes the * matching part of the string. * `slist' is a NULL-terminated array of pointers to `free'able wide strings. * `type' must contain at least one of PT_MATCHHEAD, PT_MATCHTAIL and * PT_MATCHLONGEST. If both of PT_MATCHHEAD and PT_MATCHTAIL are specified, * PT_MATCHLONGEST must be specified too. * Elements of `slist' may be modified and/or `realloc'ed in this function. */ void match_each(void **restrict slist, const wchar_t *restrict pattern, paramexptype_T type) { xfnmflags_T flags = 0; assert(type & (PT_MATCHHEAD | PT_MATCHTAIL | PT_MATCHLONGEST)); if (type & PT_MATCHHEAD) flags |= XFNM_HEADONLY; if (type & PT_MATCHTAIL) flags |= XFNM_TAILONLY; if (!(type & PT_MATCHLONGEST)) flags |= XFNM_SHORTEST; xfnmatch_T *xfnm = xfnm_compile(pattern, flags); if (xfnm == NULL) return; for (size_t i = 0; slist[i] != NULL; i++) { wchar_t *s = slist[i]; xfnmresult_T result = xfnm_wmatch(xfnm, s); if (result.start != (size_t) -1) { xwcsbuf_T buf; wb_initwith(&buf, s); wb_remove(&buf, result.start, result.end - result.start); slist[i] = wb_towcs(&buf); } } xfnm_free(xfnm); } /* Matches each string in array `slist' to pattern `pattern' and substitutes * the matching portions with `subst'. * `slist' is a NULL-terminated array of pointers to `free'able wide strings. * `type' may contain PT_MATCHHEAD, PT_MATCHTAIL and PT_SUBSTALL. * PT_MATCHLONGEST is always assumed to be specified. * Elements of `slist' may be modified and/or `realloc'ed in this function. */ void subst_each(void **restrict slist, const wchar_t *pattern, const wchar_t *subst, paramexptype_T type) { xfnmflags_T flags = 0; if (type & PT_MATCHHEAD) flags |= XFNM_HEADONLY; if (type & PT_MATCHTAIL) flags |= XFNM_TAILONLY; xfnmatch_T *xfnm = xfnm_compile(pattern, flags); if (xfnm == NULL) return; for (size_t i = 0; slist[i] != NULL; i++) { wchar_t *s = slist[i]; slist[i] = xfnm_subst(xfnm, s, subst, type & PT_SUBSTALL); free(s); } xfnm_free(xfnm); } /* Concatenates the wide strings in the specified array. * Array `*values' must be a NULL-terminated array of pointers to wide strings. * The strings are concatenated into one, each separated by the first $IFS * character. The separators are backslashed if `escape' is true. * The array and its element strings are all freed in this function. * The return value is a pointer to the newly malloced wide string that is the * result of concatenation. */ wchar_t *concatenate_values(void **values, bool escape) { wchar_t *first = values[0]; if (first != NULL && values[1] == NULL) { // no actual concatenation needed free(values); return first; } const wchar_t *ifs = getvar(L VAR_IFS); wchar_t padding[] = { L'\\', ifs != NULL ? ifs[0] : L' ', L'\0' }; wchar_t *result = joinwcsarray(values, escape ? padding : &padding[1]); plfree(values, free); return result; } /* Like `concatenate_values', but returns a pointer to a newly malloced * NULL-terminated array containing the concatenated string. */ void **concatenate_values_into_array(void **values, bool escape) { if (values[0] != NULL && values[1] == NULL) return values; void **results = xmallocn(2, sizeof *values); results[0] = concatenate_values(values, escape); results[1] = NULL; return results; } /* Substitutes each string in the specified array with a string that contains * the number of characters in the original string. * `slist' is a NULL-terminated array of pointers to `free'able wide strings. * The strings are `realloc'ed and modified in this function. */ void subst_length_each(void **slist) { for (size_t i = 0; slist[i] != NULL; i++) { size_t len = wcslen(slist[i]); free(slist[i]); slist[i] = malloc_wprintf(L"%zu", len); } } /********** Brace Expansions **********/ /* Performs brace expansion in each element of the specified array. * `values' is an array of pointers to `free'able wide strings to be expanded. * `splits' is an array of pointers to `free'able splittability strings. * `values' and 'splits' must contain the same number of elements. * Both the arrays must be NULL-terminated and their elements are freed in this * function. The arrays themselves are not freed. * Newly malloced results are added to `valuelist' and `splitlist'. */ void expand_brace_each(void **restrict values, void **restrict splits, plist_T *restrict valuelist, plist_T *restrict splitlist) { while (*values != NULL) { expand_brace(*values, *splits, valuelist, splitlist); values++, splits++; } } /* Performs brace expansion in the specified single word. * `split' is the splittability string corresponding to `word'. * `word' and `split' are freed in this function. * `Free'able results are added to `valuelist' and `splitlist'. */ void expand_brace(wchar_t *restrict const word, char *restrict const split, plist_T *restrict valuelist, plist_T *restrict splitlist) { wchar_t *c = word; start: c = escaped_wcspbrk(c, L"{"); if (c == NULL || *++c == L'\0') { /* don't expand if there is no L'{' or L'{' is at the end of string */ pl_add(valuelist, word); pl_add(splitlist, split); return; } else if (try_expand_brace_sequence(word, split, c, valuelist, splitlist)){ return; } plist_T splitpoints; unsigned nest; /* collect pointers to characters where the word is split */ /* The pointers point to the character just after L'{', L',' or L'}'. */ pl_init(&splitpoints); pl_add(&splitpoints, c); nest = 0; while ((c = escaped_wcspbrk(c, L"{,}")) != NULL) { switch (*c++) { case L'{': nest++; break; case L',': if (nest == 0) pl_add(&splitpoints, c); break; case L'}': if (nest > 0) { nest--; break; } else if (splitpoints.length == 1) { goto restart; } else { pl_add(&splitpoints, c); goto done; } } } restart: /* if there is no L',' or L'}' corresponding to L'{', * find the next L'{' and try again */ c = splitpoints.contents[0]; pl_destroy(&splitpoints); goto start; done:; #define idx(p) ((wchar_t *) (p) - word) #define wtos(p) (split + idx(p)) size_t lastelemindex = splitpoints.length - 1; size_t headlen = idx(splitpoints.contents[0]) - 1; size_t taillen = wcslen(splitpoints.contents[lastelemindex]); for (size_t i = 0; i < lastelemindex; i++) { xwcsbuf_T buf; xstrbuf_T sbuf; wb_init(&buf); sb_init(&sbuf); wb_ncat_force(&buf, word, headlen); sb_ncat_force(&sbuf, split, headlen); size_t len = (wchar_t *) splitpoints.contents[i + 1] - (wchar_t *) splitpoints.contents[i ] - 1; wb_ncat_force(&buf, splitpoints.contents[i], len); sb_ncat_force(&sbuf, wtos(splitpoints.contents[i]), len); wb_ncat_force(&buf, splitpoints.contents[lastelemindex], taillen); sb_ncat_force(&sbuf, wtos(splitpoints.contents[lastelemindex]), taillen); assert(buf.length == sbuf.length); /* expand the remaining portion recursively */ expand_brace(wb_towcs(&buf), sb_tostr(&sbuf), valuelist, splitlist); } pl_destroy(&splitpoints); free(word); free(split); #undef idx #undef wtos } /* Tries numeric brace expansion like "{01..05}". * If unsuccessful, this function returns false without any side effects. * If successful, `word' and `split' are freed and the full expansion results * are added to `valuelist' and `splitlist'. * `startc' is a pointer to the character right after L'{' in `word'. */ bool try_expand_brace_sequence( wchar_t *word, char *restrict split, wchar_t *startc, plist_T *restrict valuelist, plist_T *restrict splitlist) { long start, end, delta, value; wchar_t *dotp, *dotbracep, *bracep, *c; int startlen, endlen, len, wordlen; bool sign = false; assert(startc[-1] == L'{'); c = startc; /* parse the starting point */ dotp = wcschr(c, L'.'); if (dotp == NULL || c == dotp || dotp[1] != L'.') return false; startlen = has_leading_zero(c, &sign) ? (dotp - c) : 0; errno = 0; start = wcstol(c, &c, 10); if (errno != 0 || c != dotp) return false; c = dotp + 2; /* parse the ending point */ dotbracep = wcspbrk(c, L".}"); if (dotbracep == NULL || c == dotbracep || (dotbracep[0] == L'.' && dotbracep[1] != L'.')) return false; endlen = has_leading_zero(c, &sign) ? (dotbracep - c) : 0; errno = 0; end = wcstol(c, &c, 10); if (errno != 0 || c != dotbracep) return false; /* parse the delta */ if (dotbracep[0] == L'.') { assert(dotbracep[1] == L'.'); c = dotbracep + 2; bracep = wcschr(c, L'}'); if (bracep == NULL || c == bracep) return false; errno = 0; delta = wcstol(c, &c, 10); if (delta == 0 || errno != 0 || c != bracep) return false; } else { assert(dotbracep[0] == L'}'); bracep = dotbracep; if (start <= end) delta = 1; else delta = -1; } /* expand the sequence */ value = start; len = (startlen > endlen) ? startlen : endlen; wordlen = wcslen(word); do { xwcsbuf_T buf; xstrbuf_T sbuf; wb_init(&buf); sb_init(&sbuf); wb_ncat_force(&buf, word, startc - 1 - word); sb_ncat_force(&sbuf, split, startc - 1 - word); int plen = wb_wprintf(&buf, sign ? L"%0+*ld" : L"%0*ld", len, value); if (plen >= 0) sb_ccat_repeat(&sbuf, 0, plen); wb_ncat_force(&buf, bracep + 1, wordlen - (bracep + 1 - word)); sb_ncat_force(&sbuf, split + (bracep + 1 - word), wordlen - (bracep + 1 - word)); assert(buf.length == sbuf.length); /* expand the remaining portion recursively */ expand_brace(wb_towcs(&buf), sb_tostr(&sbuf), valuelist, splitlist); if (delta >= 0) { if (LONG_MAX - delta < value) break; } else { if (LONG_MIN - delta > value) break; } value += delta; } while (delta >= 0 ? value <= end : value >= end); free(word); free(split); return true; } /* Checks if the specified numeral starts with a L'0'. * Leading spaces are ignored. * If the numeral has a plus sign L'+', true is assigned to `*sign'. * If not, false is assigned. */ bool has_leading_zero(const wchar_t *restrict s, bool *restrict sign) { while (iswspace(*s)) s++; if (*s == L'+') { *sign = true; s++; } else if (*s == L'-') { s++; } return *s == L'0'; } /********** Field Splitting **********/ /* Performs field splitting. * `valuelist' is a NULL-terminated array of pointers to wide strings to split. * `splitlist' is an array of pointers to corresponding splittability strings. * `valuelist' and `splitlist' are `plfree'ed in this function. * The results are added to `dest'. */ void fieldsplit_all(void **restrict valuelist, void **restrict splitlist, plist_T *restrict dest) { void **restrict s; void **restrict t; const wchar_t *ifs; ifs = getvar(L VAR_IFS); if (ifs == NULL) ifs = DEFAULT_IFS; for (s = valuelist, t = splitlist; *s != NULL; s++, t++) fieldsplit(*s, *t, ifs, dest); free(valuelist); free(splitlist); } /* Performs field splitting. * `s' is the word to split and freed in this function. * `split' is the splittability string corresponding to `s' and also freed. * The results are added to `dest' as newly-malloced wide strings. * `ifs' must not be NULL. */ void fieldsplit(wchar_t *restrict s, char *restrict split, const wchar_t *restrict ifs, plist_T *restrict dest) { plist_T fields; pl_init(&fields); extract_fields(s, split, true, ifs, &fields); assert(fields.length % 2 == 0); for (size_t i = 0; i < fields.length; i += 2) { const wchar_t *start = fields.contents[i], *end = fields.contents[i+1]; pl_add(dest, xwcsndup(start, end - start)); } pl_destroy(&fields); free(s); free(split); } /* Extracts fields from a string. * `s' is the word to split. * `split' is the splittability string corresponding to `s'. It must be at least * as long as `wcslen(s)'. * If `escaped' is true, backslashes in `s' are treated as escapes. But * backslashes do not prevent splitting. * `ifs' must not be NULL. * * The results are appended to `dest'. If n fields are found, 2n pointers are * appended to `dest'. The first pointer points to the first character of the * first field in `s'. The second to the character past the last character of * the first field. The third to the first character of the second field. And so * on. * * The word is split at characters that are contained in `ifs' and whose * corresponding character in the splittability string is non-zero. Refer to * POSIX for how whitespaces are treated in field splitting. * * If an IFS non-whitespace delimits an empty field, the field is assumed just * before the non-whitespace delimiter. The empty last field is removed if * `shopt_emptylastfield' is false. * * The return value is a pointer to the end of the input string (but before * trailing IFS whitespaces). */ /* Split examples (assuming `ifs' = L" -" and `shopt_emptylastfield' is true) * "" -> "" * " " -> "" * " abc 123 " -> "abc" "123" * " abc 123 " -> "abc" "123" * "-abc-123-" -> "" "abc" "123" "" * " - abc - 123 - " -> "" "abc" "123" "" * "abc--123" -> "abc" "" "123" * "abc - - 123" -> "abc" "" "123" */ wchar_t *extract_fields(const wchar_t *restrict s, const char *restrict split, bool escaped, const wchar_t *restrict ifs, plist_T *restrict dest) { size_t index = 0; size_t ifswhitestartindex; size_t oldlen = dest->length; /* true when the currently skipping IFS whitespaces immediately follow a * previously split field. */ bool afterfield = false; for (;;) { ifswhitestartindex = index; index += skip_ifs_whitespaces(&s[index], &split[index], escaped, ifs); /* extract next field, if any */ size_t fieldstartindex = index; index += skip_field(&s[index], &split[index], escaped, ifs); if (index != fieldstartindex) { pl_add(pl_add(dest, &s[fieldstartindex]), &s[index]); afterfield = true; continue; } /* Now the current char is either null or a IFS non-whitespace. */ if (!afterfield) add_empty_field(dest, &s[index]); /* skip (only one) IFS non-whitespace */ size_t ifsstartindex = index; index += skip_ifs(&s[index], &split[index], escaped, ifs); if (index != ifsstartindex) { afterfield = false; continue; } /* Now the current char is null. We're done. */ break; } /* remove the empty last field */ size_t newlen = dest->length; if (!shopt_emptylastfield && newlen - oldlen >= 2 * 2 && dest->contents[newlen - 2] == dest->contents[newlen - 1]) pl_remove(dest, newlen - 2, 2); assert(dest->length - oldlen >= 2); return (wchar_t *) &s[ifswhitestartindex]; } /* If `*s' is a (possibly escaped if `escaped') IFS character, returns the * number of characters to skip it. Otherwise returns zero. */ size_t skip_ifs(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) { size_t i = 0; if (escaped && s[i] == L'\\') i++; if (s[i] == L'\0') return 0; if (split[i] && wcschr(ifs, s[i]) != NULL) return i + 1; else return 0; } /* Returns the length of IFS whitespace sequence starting at `*s'. */ size_t skip_ifs_whitespaces(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) { size_t total = 0; for (;;) { size_t current = skip_ifs(&s[total], &split[total], escaped, ifs); if (current == 0 || !iswspace(s[total + current - 1])) return total; total += current; } } /* Returns the length of a field starting at `*s'. */ size_t skip_field(const wchar_t *s, const char *split, bool escaped, const wchar_t *ifs) { size_t index = 0; for (;;) { size_t saveindex = index; if (escaped && s[index] == L'\\') index++; if (s[index] == L'\0') return saveindex; if (split[index] && wcschr(ifs, s[index]) != NULL) return saveindex; index++; } } void add_empty_field(plist_T *dest, const wchar_t *p) { pl_add(dest, p); pl_add(dest, p); } /********** Escaping **********/ /* Unquotes the specified single-quoted string and adds it to the specified * buffer. * `ss' is a pointer to a pointer to the opening quote in the string. * `*ss' is incremented so that it points to the closing quote. * If `escape' is true, all the characters added are backslashed. */ void add_sq(const wchar_t *restrict *ss, xwcsbuf_T *restrict buf, bool escape) { assert(**ss == L'\''); for (;;) { (*ss)++; switch (**ss) { case L'\0': assert(false); case L'\'': return; default: if (escape) wb_wccat(buf, L'\\'); wb_wccat(buf, **ss); break; } } } /* Backslashes characters in `s' that are contained in `t'. * Returns a newly-malloced wide string. * `t' may be NULL, in which case all the characters are backslashed. */ wchar_t *escape(const wchar_t *restrict s, const wchar_t *restrict t) { xwcsbuf_T buf; wb_init(&buf); for (size_t i = 0; s[i] != L'\0'; i++) { if (t == NULL || wcschr(t, s[i]) != NULL) wb_wccat(&buf, L'\\'); wb_wccat(&buf, s[i]); } return wb_towcs(&buf); } /* Same as `escape', except that the first argument is freed. */ wchar_t *escapefree(wchar_t *restrict s, const wchar_t *restrict t) { if (t != NULL && wcspbrk(s, t) == NULL) { return s; } else { wchar_t *result = escape(s, t); free(s); return result; } } /* Removes backslash escapes. The result is a newly malloced string. * If there is an unescaped backslash before the null character, the backslash * is ignored. */ wchar_t *unescape(const wchar_t *s) { xwcsbuf_T buf; wb_init(&buf); for (size_t i = 0; s[i] != L'\0'; i++) { if (s[i] == L'\\') { if (s[i + 1] == L'\0') break; else i++; } wb_wccat(&buf, s[i]); } return wb_towcs(&buf); } /* Same as `unescape', except that the first argument is freed. */ wchar_t *unescapefree(wchar_t *s) { if (wcschr(s, L'\\') == NULL) { return s; } else { wchar_t *result = unescape(s); free(s); return result; } } /* Quotes the specified string using backslashes and single-quotes. The result * is suitable for re-parsing as a shell command word that would expand to the * original string. The result is a newly malloced string. */ wchar_t *quote_as_word(const wchar_t *s) { xwcsbuf_T buf; wb_init(&buf); wb_quote_as_word(&buf, s); return wb_towcs(&buf); } /* Quotes string `s' using backslashes and single-quotes. The result * is suitable for re-parsing as a shell command word that would expand to the * original string. The result is appended to the given buffer, which must * have been initialized before calling this function. */ xwcsbuf_T *wb_quote_as_word(xwcsbuf_T *restrict buf, const wchar_t *restrict s) { if (*s == L'\0') { wb_wccat(buf, L'\''); wb_wccat(buf, L'\''); return buf; } while (*s != L'\0') { if (*s == L'\'') { wb_wccat(buf, L'\\'); wb_wccat(buf, L'\''); s++; continue; } const wchar_t *end = s; while (*end == L'.' || *end == L'-' || *end == L'_' || *end == L'/' || iswalnum(*end)) end++; if (*end == L'\0' || *end == L'\'') { /* No characters have to be quoted until `*end'. */ wb_ncat_force(buf, s, end - s); s = end; continue; } /* Quote characters until the next single-quote or end-of-string. */ wb_ensuremax(buf, buf->length + (end - s) + 2); wb_wccat(buf, L'\''); while (*s != L'\0' && *s != L'\'') { wb_wccat(buf, *s); s++; } wb_wccat(buf, L'\''); } return buf; } /* Removes quotes (', ", \). The result is a newly malloced string. */ wchar_t *unquote(const wchar_t *s) { bool indq = false; xwcsbuf_T buf; wb_init(&buf); for (;;) { switch (*s) { case L'\0': return wb_towcs(&buf); case L'\'': if (indq) goto default_case; add_sq(&s, &buf, false); break; case L'"': indq = !indq; break; case L'\\': if (s[1] != L'\0' && (!indq || wcschr(CHARS_ESCAPABLE, s[1]))) { wb_wccat(&buf, s[1]); s += 2; continue; } /* falls thru! */ default: default_case: wb_wccat(&buf, *s); break; } s++; } } /* Like `wcspbrk', but ignores backslashed characters in `s'. */ wchar_t *escaped_wcspbrk(const wchar_t *s, const wchar_t *accept) { for (; *s != L'\0'; s++) { if (*s == L'\\') { s++; if (*s == L'\0') break; continue; } if (wcschr(accept, *s) != NULL) return (wchar_t *) s; } return NULL; } /* Removes characters in `reject' from `s'. * Backslash escapes in `s' are recognized. Escapes and escaped characters are * kept in the result. * The result is a newly malloced string. */ wchar_t *escaped_remove(const wchar_t *s, const wchar_t *reject) { xwcsbuf_T result; wb_init(&result); for (;;) { const wchar_t *rejectchar = escaped_wcspbrk(s, reject); if (rejectchar == NULL) break; wb_ncat_force(&result, s, rejectchar - s); s = rejectchar + 1; } wb_cat(&result, s); return wb_towcs(&result); } /* Like `escaped_remove', but frees `s' before returning the result. */ wchar_t *escaped_remove_free(wchar_t *s, const wchar_t *reject) { wchar_t *result = escaped_remove(s, reject); free(s); return result; } /********** File Name Expansion (Glob) **********/ /* Makes a option value from the current shell settings. */ enum wglobflags_T get_wglobflags(void) { enum wglobflags_T flags = 0; if (!shopt_caseglob) flags |= WGLB_CASEFOLD; if (shopt_dotglob) flags |= WGLB_PERIOD; if (shopt_markdirs) flags |= WGLB_MARK; if (shopt_extendedglob) flags |= WGLB_RECDIR; return flags; } /* Performs file name expansion to the specified patterns. * `patterns' is a NULL-terminated array of pointers to `free'able wide strings * cast to (void *). `patterns' is `plfree'd in this function. * The results are added to `list' as newly-malloced wide strings. */ void glob_all(void **restrict patterns, plist_T *restrict list) { enum wglobflags_T flags = get_wglobflags(); bool unblock = false; for (size_t i = 0; patterns[i] != NULL; i++) { wchar_t *pat = patterns[i]; if (is_pathname_matching_pattern(pat)) { if (!unblock) { set_interruptible_by_sigint(true); unblock = true; } size_t oldlen = list->length; wglob(pat, flags, list); if (!shopt_nullglob && oldlen == list->length) goto addpattern; free(pat); } else { /* If the pattern doesn't contain characters like L'*' and L'?', * we don't need to glob. */ addpattern: pl_add(list, unescapefree(pat)); } } if (unblock) set_interruptible_by_sigint(false); free(patterns); } /********** Auxiliary functions **********/ /* Performs parameter expansion, command substitution of the form "$(...)", and * arithmetic expansion in the specified string. * If `name' is non-NULL, it is printed in error messages on error. * If `esc' is true, backslashes preceding $, `, \ are removed. Otherwise, * no quotations are removed. * Returns a newly malloced string if successful. Otherwise NULL is returned. * This function uses the parser, so the parser state must have been saved if * this function is called during another parse. */ wchar_t *parse_and_expand_string(const wchar_t *s, const char *name, bool esc) { struct input_wcs_info_T winfo = { .src = s, }; parseparam_T info = { .print_errmsg = true, .enable_verbose = false, .enable_alias = true, .filename = name, .lineno = 1, .input = input_wcs, .inputinfo = &winfo, .interactive = false, }; wordunit_T *word; wchar_t *result; if (!parse_string(&info, &word)) return NULL; result = expand_single_and_unescape(word, TT_NONE, false, !esc); wordfree(word); return result; } /* This function is called when an expansion error occurred. * The shell exits if it is non-interactive. */ void maybe_exit_on_error(void) { if (shell_initialized && !is_interactive_now) exit_shell_with_status(Exit_EXPERROR); } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/expand.h000066400000000000000000000057711354143602500140610ustar00rootroot00000000000000/* Yash: yet another shell */ /* expand.h: word expansion */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_EXPAND_H #define YASH_EXPAND_H #include #define DEFAULT_IFS L" \t\n" /* characters that can be escaped with a backslash inside double-quotes. */ #define CHARS_ESCAPABLE L"$`\"\\" /* type of tilde expansion */ typedef enum { TT_NONE, TT_SINGLE, TT_MULTI, } tildetype_T; struct wordunit_T; struct plist_T; extern _Bool expand_line( void *const *restrict args, int *restrict argcp, void ***restrict argvp) __attribute__((nonnull)); extern _Bool expand_multiple( const struct wordunit_T *restrict w, struct plist_T *restrict list) __attribute__((nonnull(2))); extern wchar_t *expand_single(const struct wordunit_T *arg, tildetype_T tilde, _Bool processquotes, _Bool escapeall) __attribute__((malloc,warn_unused_result)); extern wchar_t *expand_single_and_unescape(const struct wordunit_T *arg, tildetype_T tilde, _Bool processquotes, _Bool escapeall) __attribute__((malloc,warn_unused_result)); extern char *expand_single_with_glob( const struct wordunit_T *arg, tildetype_T tilde) __attribute__((malloc,warn_unused_result)); extern wchar_t *extract_fields( const wchar_t *restrict s, const char *restrict split, _Bool escaped, const wchar_t *restrict ifs, struct plist_T *restrict dest) __attribute__((nonnull)); struct xwcsbuf_T; extern wchar_t *escape(const wchar_t *restrict s, const wchar_t *restrict t) __attribute__((nonnull(1),malloc,warn_unused_result)); extern wchar_t *escapefree( wchar_t *restrict s, const wchar_t *restrict t) __attribute__((nonnull(1),malloc,warn_unused_result)); extern wchar_t *unescape(const wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); extern wchar_t *unescapefree(wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); extern wchar_t *quote_as_word(const wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); extern struct xwcsbuf_T *wb_quote_as_word( struct xwcsbuf_T *restrict buf, const wchar_t *restrict s) __attribute__((nonnull)); extern wchar_t *unquote(const wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); extern wchar_t *parse_and_expand_string( const wchar_t *s, const char *name, _Bool esc) __attribute__((nonnull(1),malloc,warn_unused_result)); #endif /* YASH_EXPAND_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/hashtable.c000066400000000000000000000336751354143602500145340ustar00rootroot00000000000000/* Yash: yet another shell */ /* hashtable.c: hashtable library */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "hashtable.h" #include #include #include #include #include "util.h" /* A hashtable is a mapping from keys to values. * Keys and values are all of type (void *). * NULL is allowed as a value, but not as a key. * The capacity of a hashtable is always no less than one. */ /* The hashtable_T structure is defined as follows: * struct hashtable_T { * size_t capacity; * size_t count; * hashfunc_T *hashfunc; * keycmp keycmp; * size_t emptyindex; * size_t tailindex; * size_t *indices; * struct hash_entry *entries; * } * `capacity' is the size of array `entries'. * `count' is the number of entries contained in the hashtable. * `hashfunc' is a pointer to the hash function. * `keycmp' is a pointer to the function that compares keys. * `emptyindex' is the index of the first empty entry. * `tailindex' is the index of the first tail entry. * `indices' is a pointer to the bucket array. * `entries' is a pointer to the array of entries. * * The collision resolution strategy used in this implementation is a kind of * separate chaining, but it differs from normal chaining in that entries are * stored in a single array (`entries'). An advantage over normal chaining, * which stores entries in linked lists, is spatial locality: entries can be * quickly referenced because they are collected in one array. Another advantage * is that we don't have to call `malloc' or `free' each time an entry is added * or removed. */ //#define DEBUG_HASH 1 #if DEBUG_HASH /* For debugging */ # define DEBUG_PRINT_STATISTICS(ht) (print_statistics(ht)) # include static void print_statistics(const hashtable_T *ht); #else # define DEBUG_PRINT_STATISTICS(ht) ((void) 0) #endif /* The null index */ #define NOTHING ((size_t) -1) /* hashtable entry */ struct hash_entry { size_t next; hashval_T hash; kvpair_T kv; }; /* An entry is occupied iff `.kv.key' is non-NULL. * When an entry is unoccupied, the values of the other members of the entry are * unspecified. */ /* Initializes a hashtable with the specified capacity. * `hashfunc' is a hash function to hash keys. * `keycmp' is a function that compares two keys. */ hashtable_T *ht_initwithcapacity( hashtable_T *ht, hashfunc_T *hashfunc, keycmp_T *keycmp, size_t capacity) { if (capacity == 0) capacity = 1; ht->capacity = capacity; ht->count = 0; ht->hashfunc = hashfunc; ht->keycmp = keycmp; ht->emptyindex = NOTHING; ht->tailindex = 0; ht->indices = xmallocn(capacity, sizeof *ht->indices); ht->entries = xmallocn(capacity, sizeof *ht->entries); for (size_t i = 0; i < capacity; i++) { ht->indices[i] = NOTHING; ht->entries[i].kv.key = NULL; } return ht; } /* Changes the capacity of the specified hashtable. * If the specified new capacity is smaller than the number of the entries in * the hashtable, the capacity is not changed. * Note that the capacity cannot be zero. If `newcapacity' is zero, it is * assumed to be one. */ /* Capacity should be an odd integer, especially a prime number. */ hashtable_T *ht_setcapacity(hashtable_T *ht, size_t newcapacity) { if (newcapacity == 0) newcapacity = 1; if (newcapacity < ht->count) newcapacity = ht->count; size_t oldcapacity = ht->capacity; size_t *oldindices = ht->indices; size_t *newindices = xmallocn(newcapacity, sizeof *ht->indices); struct hash_entry *oldentries = ht->entries; struct hash_entry *newentries = xmallocn(newcapacity, sizeof *ht->entries); size_t tail = 0; for (size_t i = 0; i < newcapacity; i++) { newindices[i] = NOTHING; newentries[i].kv.key = NULL; } /* move the data from oldentries to newentries */ for (size_t i = 0; i < oldcapacity; i++) { void *key = oldentries[i].kv.key; if (key != NULL) { hashval_T hash = oldentries[i].hash; size_t newindex = (size_t) hash % newcapacity; newentries[tail] = (struct hash_entry) { .next = newindices[newindex], .hash = hash, .kv = oldentries[i].kv, }; newindices[newindex] = tail; tail++; } } free(oldindices); free(oldentries); ht->capacity = newcapacity; ht->emptyindex = NOTHING; ht->tailindex = tail; ht->indices = newindices; ht->entries = newentries; return ht; } /* Increases the capacity as large as necessary * so that the capacity is no less than the specified. */ hashtable_T *ht_ensurecapacity(hashtable_T *ht, size_t capacity) { if (capacity <= ht->capacity) return ht; size_t cap15 = ht->capacity + (ht->capacity >> 1); if (capacity < cap15) capacity = cap15; if (capacity < ht->capacity + 6) capacity = ht->capacity + 6; return ht_setcapacity(ht, capacity); } /* Removes all the entries of a hashtable. * If `freer' is non-NULL, it is called for each entry removed (in an * unspecified order). * The capacity of the hashtable is not changed. */ hashtable_T *ht_clear(hashtable_T *ht, void freer(kvpair_T kv)) { size_t *indices = ht->indices; struct hash_entry *entries = ht->entries; if (ht->count == 0) return ht; for (size_t i = 0, cap = ht->capacity; i < cap; i++) { indices[i] = NOTHING; if (entries[i].kv.key != NULL) { if (freer) freer(entries[i].kv); entries[i].kv.key = NULL; } } ht->count = 0; ht->emptyindex = NOTHING; ht->tailindex = 0; return ht; } /* Returns the entry whose key is equal to the specified `key', * or { NULL, NULL } if `key' is NULL or there is no such entry. */ kvpair_T ht_get(const hashtable_T *ht, const void *key) { if (key != NULL) { hashval_T hash = ht->hashfunc(key); size_t index = ht->indices[(size_t) hash % ht->capacity]; while (index != NOTHING) { struct hash_entry *entry = &ht->entries[index]; if (entry->hash == hash && ht->keycmp(entry->kv.key, key) == 0) return entry->kv; index = entry->next; } } return (kvpair_T) { NULL, NULL, }; } /* Makes a new entry with the specified key and value, * removing and returning the old entry for the key. * If there is no such old entry, { NULL, NULL } is returned. * `key' must not be NULL. */ kvpair_T ht_set(hashtable_T *ht, const void *key, const void *value) { assert(key != NULL); /* if there is an entry with the specified key, simply replace the value */ hashval_T hash = ht->hashfunc(key); size_t mhash = (size_t) hash % ht->capacity; size_t index = ht->indices[mhash]; struct hash_entry *entry; while (index != NOTHING) { entry = &ht->entries[index]; if (entry->hash == hash && ht->keycmp(entry->kv.key, key) == 0) { kvpair_T oldkv = entry->kv; entry->kv = (kvpair_T) { (void *) key, (void *) value, }; DEBUG_PRINT_STATISTICS(ht); return oldkv; } index = entry->next; } /* No entry with the specified key was found; we add a new entry. */ index = ht->emptyindex; if (index != NOTHING) { /* if there is an empty entry, use it */ entry = &ht->entries[index]; ht->emptyindex = entry->next; } else { /* if there is no empty entry, use a tail entry */ ht_ensurecapacity(ht, ht->count + 1); mhash = (size_t) hash % ht->capacity; index = ht->tailindex++; entry = &ht->entries[index]; } *entry = (struct hash_entry) { .next = ht->indices[mhash], .hash = hash, .kv = (kvpair_T) { (void *) key, (void *) value, }, }; ht->indices[mhash] = index; ht->count++; DEBUG_PRINT_STATISTICS(ht); return (kvpair_T) { NULL, NULL, }; } /* Removes and returns the entry with the specified key. * If `key' is NULL or there is no such entry, { NULL, NULL } is returned. */ kvpair_T ht_remove(hashtable_T *ht, const void *key) { if (key != NULL) { hashval_T hash = ht->hashfunc(key); size_t *indexp = &ht->indices[(size_t) hash % ht->capacity]; while (*indexp != NOTHING) { size_t index = *indexp; struct hash_entry *entry = &ht->entries[index]; if (entry->hash == hash && ht->keycmp(entry->kv.key, key) == 0) { kvpair_T oldkv = entry->kv; *indexp = entry->next; entry->next = ht->emptyindex; ht->emptyindex = index; entry->kv.key = NULL; ht->count--; return oldkv; } indexp = &entry->next; } } return (kvpair_T) { NULL, NULL, }; } #if 0 /* Calls the specified function `f' for each entry in the specified hashtable. * The order in which the entries are applied the function to is unspecified. * If `f' returns a non-zero value for some entry, `f' is not called any more * and `ht_each' immediately returns the non-zero value. Otherwise, that is, * if `f' returns zero for all the entry, `ht_each' also returns zero. * You must not add or remove any entry inside function `f'. */ int ht_each(const hashtable_T *ht, int f(kvpair_T kv)) { struct hash_entry *entries = ht->entries; for (size_t i = 0, cap = ht->capacity; i < cap; i++) { kvpair_T kv = entries[i].kv; if (kv.key != NULL) { int r = f(kv); if (r != 0) return r; } } return 0; } #endif /* Iterates the entries of the specified hashtable. * When starting new iteration, `*indexp' must have been initialized to zero. * Each time this function is called, it updates `*indexp' and returns one * entry. * You must not change the value of `*indexp' from outside this function or * add/remove any entry in the hashtable until the iteration finishes. * Each entry is returned exactly once, in an unspecified order. * If there is no more entry to be iterated, { NULL, NULL } is returned. */ kvpair_T ht_next(const hashtable_T *restrict ht, size_t *restrict indexp) { while (*indexp < ht->capacity) { kvpair_T kv = ht->entries[*indexp].kv; (*indexp)++; if (kv.key != NULL) return kv; } return (kvpair_T) { NULL, NULL, }; } /* Returns a newly malloced array of key-value pairs that contains all the * elements of the specified hashtable. * The returned array is terminated by the { NULL, NULL } element. */ kvpair_T *ht_tokvarray(const hashtable_T *ht) { kvpair_T *array = xmalloce(ht->count, 1, sizeof *array); size_t index = 0; for (size_t i = 0; i < ht->capacity; i++) { if (ht->entries[i].kv.key != NULL) array[index++] = ht->entries[i].kv; } assert(index == ht->count); array[index] = (kvpair_T) { NULL, NULL, }; return array; } /* A hash function for a byte string. * The argument is a pointer to a byte string (const char *). * You can use `htstrcmp' as a corresponding comparison function. */ hashval_T hashstr(const void *s) { /* The hashing algorithm is FNV hash. * Cf. http://www.isthe.com/chongo/tech/comp/fnv/ */ const unsigned char *c = s; hashval_T h = 0; while (*c != '\0') h = (h ^ (hashval_T) *c++) * FNVPRIME; return h; } /* A hash function for a wide string. * The argument is a pointer to a wide string (const wchar_t *). * You can use `htwcscmp' for a corresponding comparison function. */ hashval_T hashwcs(const void *s) { /* The hashing algorithm is a slightly modified version of FNV hash. * Cf. http://www.isthe.com/chongo/tech/comp/fnv/ */ const wchar_t *c = s; hashval_T h = 0; while (*c != L'\0') h = (h ^ (hashval_T) *c++) * FNVPRIME; return h; } /* A comparison function for wide strings. * The arguments are pointers to wide strings (const wchar_t *). * You can use `hashwcs' for a corresponding hash function. */ int htwcscmp(const void *s1, const void *s2) { return wcscmp((const wchar_t *) s1, (const wchar_t *) s2); } /* A comparison function for key-value pairs with multibyte-string keys. * The arguments are pointers to kvpair_T's (const kvpair_T *) whose keys are * multibyte strings. */ int keystrcoll(const void *k1, const void *k2) { return strcoll(((const kvpair_T *) k1)->key, ((const kvpair_T *) k2)->key); } /* A comparison function for key-value pairs with wide-string keys. * The arguments are pointers to kvpair_T's (const kvpair_T *) whose keys are * wide strings. */ int keywcscoll(const void *k1, const void *k2) { return wcscoll(((const kvpair_T *) k1)->key, ((const kvpair_T *) k2)->key); } /* `Free's the key of the specified key-value pair. * Can be used as the freer function to `ht_clear'. */ void kfree(kvpair_T kv) { free(kv.key); } /* `Free's the value of the specified key-value pair. * Can be used as the freer function to `ht_clear'. */ void vfree(kvpair_T kv) { free(kv.value); } /* `Free's the key and the value of the specified key-value pair. * Can be used as the freer function to `ht_clear'. */ void kvfree(kvpair_T kv) { free(kv.key); free(kv.value); } #if DEBUG_HASH /* Prints statistics. * This function is used in debugging. */ void print_statistics(const hashtable_T *ht) { fprintf(stderr, "DEBUG: id=%p hash->count=%zu, capacity=%zu\n", (void *) ht, ht->count, ht->capacity); fprintf(stderr, "DEBUG: hash->emptyindex=%zu, tailindex=%zu\n", ht->emptyindex, ht->tailindex); unsigned emptycount = 0, collcount = 0; for (size_t i = ht->emptyindex; i != NOTHING; i = ht->entries[i].next) emptycount++; for (size_t i = 0; i < ht->capacity; i++) if (ht->entries[i].kv.key && ht->entries[i].next != NOTHING) collcount++; fprintf(stderr, "DEBUG: hash empties=%u collisions=%u\n\n", emptycount, collcount); } #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/hashtable.h000066400000000000000000000104311354143602500145220ustar00rootroot00000000000000/* Yash: yet another shell */ /* hashtable.h: hashtable library */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_HASHTABLE_H #define YASH_HASHTABLE_H #include #include #if defined UINT64_MAX && UINT64_MAX == UINT_FAST32_MAX typedef uint64_t hashval_T; # define FNVPRIME 1099511628211 #elif defined UINT128_MAX && UINT128_MAX == UINT_FAST32_MAX typedef uint128_t hashval_T; # define FNVPRIME 309485009821345068724781371 #else typedef uint32_t hashval_T; # define FNVPRIME 16777619 #endif /* The type of hash functions. * Hash functions must return the same value for two keys that compare equal. */ typedef hashval_T hashfunc_T(const void *key); /* The type of functions that compare two keys. * Returns zero if two keys are equal, or non-zero if unequal. */ typedef int keycmp_T(const void *key1, const void *key2); typedef struct hashtable_T { size_t capacity, count; hashfunc_T *hashfunc; keycmp_T *keycmp; size_t emptyindex, tailindex; size_t *indices; struct hash_entry *entries; } hashtable_T; typedef struct kvpair_T { void *key, *value; } kvpair_T; static inline hashtable_T *ht_init( hashtable_T *ht, hashfunc_T *hashfunc, keycmp_T *keycmp) __attribute__((nonnull)); extern hashtable_T *ht_initwithcapacity( hashtable_T *ht, hashfunc_T *hashfunc, keycmp_T *keycmp, size_t capacity) __attribute__((nonnull)); static inline void ht_destroy(hashtable_T *ht) __attribute__((nonnull)); extern hashtable_T *ht_setcapacity(hashtable_T *ht, size_t newcapacity) __attribute__((nonnull)); extern hashtable_T *ht_ensurecapacity(hashtable_T *ht, size_t capacity) __attribute__((nonnull)); extern hashtable_T *ht_clear(hashtable_T *ht, void freer(kvpair_T kv)) __attribute__((nonnull(1))); extern kvpair_T ht_get(const hashtable_T *ht, const void *key) __attribute__((nonnull(1))); extern kvpair_T ht_set(hashtable_T *ht, const void *key, const void *value) __attribute__((nonnull(1,2))); extern kvpair_T ht_remove(hashtable_T *ht, const void *key) __attribute__((nonnull(1))); extern int ht_each(const hashtable_T *ht, int f(kvpair_T kv)) __attribute__((nonnull)); extern kvpair_T ht_next(const hashtable_T *restrict ht, size_t *restrict indexp) __attribute__((nonnull)); extern kvpair_T *ht_tokvarray(const hashtable_T *ht) __attribute__((nonnull,malloc,warn_unused_result)); extern hashval_T hashstr(const void *s) __attribute__((pure)); //extern int htstrcmp(const void *s1, const void *s2) __attribute__((pure)); // Also include to use `htstrcmp'. #define htstrcmp ((keycmp_T *) strcmp) extern hashval_T hashwcs(const void *s) __attribute__((pure)); extern int htwcscmp(const void *s1, const void *s2) __attribute__((pure)); extern int keystrcoll(const void *kv1, const void *kv2) __attribute__((pure)); extern int keywcscoll(const void *kv1, const void *kv2) __attribute__((pure)); extern void kfree(kvpair_T kv); extern void vfree(kvpair_T kv); extern void kvfree(kvpair_T kv); #ifndef HASHTABLE_DEFAULT_INIT_CAPACITY #define HASHTABLE_DEFAULT_INIT_CAPACITY 5 #endif /* Initializes the specified hashtable with the default capacity. * `hashfunc' is the hash function to hash keys. * `keycmp' is the function that compares two keys. */ hashtable_T *ht_init(hashtable_T *ht, hashfunc_T *hashfunc, keycmp_T *keycmp) { return ht_initwithcapacity( ht, hashfunc, keycmp, HASHTABLE_DEFAULT_INIT_CAPACITY); } /* Destroys the specified hashtable. * Note that this function doesn't `free' any keys or values. */ void ht_destroy(hashtable_T *ht) { free(ht->indices); free(ht->entries); } #endif /* YASH_HASHTABLE_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/history.c000066400000000000000000001436111354143602500142720ustar00rootroot00000000000000/* Yash: yet another shell */ /* history.c: command history management */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "history.h" #include #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "exec.h" #include "job.h" #include "option.h" #include "path.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "yash.h" /* The maximum size of history list (<= INT_MAX / 10) */ #ifndef MAX_HISTSIZE #define MAX_HISTSIZE 1000000 #endif #if MAX_HISTSIZE > INT_MAX / 10 #error MAX_HISTSIZE is too large #endif /* The minimum value of `max_number', * which must be at least 32768 according to POSIX. */ /* Must be a power of 10. */ #ifndef HISTORY_MIN_MAX_NUMBER #define HISTORY_MIN_MAX_NUMBER 100000 #endif #if HISTORY_MIN_MAX_NUMBER < 32768 #error HISTORY_MIN_MAX_NUMBER is too small #endif /* The default size of the history list, * which must be at least 128 according to POSIX. */ #ifndef DEFAULT_HISTSIZE #define DEFAULT_HISTSIZE 500 #endif #if DEFAULT_HISTSIZE < 128 #error DEFAULT_HISTSIZE is too small #endif #if DEFAULT_HISTSIZE > HISTORY_MIN_MAX_NUMBER #error DEFAULT_HISTSIZE cannot be larger than HISTORY_MIN_MAX_NUMBER #endif /* The main history list. */ histlist_T histlist = { .link = { Histlist, Histlist, }, .count = 0, }; /* History entries are stored in this doubly-linked list. * The `Newest' (`link.prev') member points to the newest entry and the `Oldest' * (`link.next') member points to the oldest entry. When there's no entries, * `Newest' and `Oldest' point to `histlist' itself. */ /* The maximum limit of the number of an entry. * Must always be no less than `histsize' or `HISTORY_MIN_MAX_NUMBER'. * The number of any entry is not greater than this value. */ static unsigned max_number = HISTORY_MIN_MAX_NUMBER; /* The size limit of the history list. */ static unsigned histsize = DEFAULT_HISTSIZE; /* When a new entry is added, if there are entries that has the same value as * the new entry in the `histrmdup' newest entries, those entries are removed.*/ static unsigned histrmdup = 0; /* File stream for the history file. */ static FILE *histfile = NULL; /* The revision number of the history file. A valid revision number is * non-negative. */ static long histfilerev = -1; /* The number of lines in the history file, not including the signature. * When this number reaches the threshold, the history file is refreshed. */ static size_t histfilelines = 0; /* Indicates if the history file should be flushed before it is unlocked. */ static bool histneedflush = false; /* The current time returned by `time' */ static time_t now = (time_t) -1; /* If true, the history is locked, that is, readonly. */ static bool hist_lock = false; struct search_result_T { histlink_T *prev, *next; }; static void update_time(void); static void set_histsize(unsigned newsize); static histentry_T *new_entry(unsigned number, time_t time, const char *line) __attribute__((nonnull)); static bool need_remove_entry(unsigned number) __attribute__((pure)); static void remove_entry(histentry_T *e) __attribute__((nonnull)); static void remove_last_entry(void); static void clear_all_entries(void); static struct search_result_T search_entry_by_number(unsigned number) __attribute__((pure)); static histlink_T *get_nth_newest_entry(unsigned n) __attribute__((pure)); static histlink_T *search_entry_by_prefix(const char *prefix) __attribute__((nonnull,pure)); static bool search_result_is_newer( struct search_result_T sr1, struct search_result_T sr2) __attribute__((pure)); static bool entry_is_newer(const histentry_T *e1, const histentry_T *e2) __attribute__((nonnull,pure)); static void add_histfile_pid(pid_t pid); static void remove_histfile_pid(pid_t pid); static void clear_histfile_pids(void); static void write_histfile_pids(void); static FILE *open_histfile(void); static bool lock_histfile(short type); static bool read_line(FILE *restrict f, xwcsbuf_T *restrict buf) __attribute__((nonnull)); static bool try_read_line(FILE *restrict f, xwcsbuf_T *restrict buf) __attribute__((nonnull)); static long read_signature(void); static void read_history_raw(void); static void read_history(void); static void parse_history_entry(const wchar_t *line) __attribute__((nonnull)); static void parse_removed_entry(const wchar_t *numstr) __attribute__((nonnull)); static void parse_process_id(const wchar_t *numstr) __attribute__((nonnull)); static void update_history(bool refresh); static void maybe_refresh_file(void); static int wprintf_histfile(const wchar_t *format, ...) __attribute__((nonnull)); static void write_signature(void); static void write_history_entry(const histentry_T *entry) __attribute__((nonnull)); static void refresh_file(void); static void add_history_line(const wchar_t *line, size_t maxlen) __attribute__((nonnull)); static void remove_duplicates(const char *line) __attribute__((nonnull)); /* Updates the value of `now'. */ void update_time(void) { now = time(NULL); } /* Changes `histsize' to `newsize' and accordingly sets `max_number'. */ /* `histsize' is set only once in initialization. */ void set_histsize(unsigned newsize) { if (newsize > MAX_HISTSIZE) newsize = MAX_HISTSIZE; if (newsize == 0) newsize = 1; histsize = newsize; max_number = HISTORY_MIN_MAX_NUMBER; while (max_number < 2 * histsize) max_number *= 10; /* `max_number' is the smallest power of 10 that is not less than * 2 * histsize. */ } /* Adds a new history entry to the end of `histlist'. * Some oldest entries may be removed in this function if they conflict with the * new one or the list is full. */ histentry_T *new_entry(unsigned number, time_t time, const char *line) { assert(!hist_lock); assert(number > 0); assert(number <= max_number); while (need_remove_entry(number)) remove_entry(ashistentry(histlist.Oldest)); histentry_T *new = xmallocs(sizeof *new, add(strlen(line), 1), sizeof *new->value); new->Prev = histlist.Newest; new->Next = Histlist; histlist.Newest = new->Prev->next = &new->link; new->number = number; new->time = time; strcpy(new->value, line); histlist.count++; assert(histlist.count <= histsize); return new; } bool need_remove_entry(unsigned number) { if (histlist.count == 0) return false; if (histlist.count >= histsize) return true; unsigned oldest = ashistentry(histlist.Oldest)->number; unsigned newest = ashistentry(histlist.Newest)->number; if (oldest <= newest) return oldest <= number && number <= newest; else return oldest <= number || number <= newest; } /* Removes the specified entry from `histlist'. */ void remove_entry(histentry_T *entry) { assert(!hist_lock); assert(&entry->link != Histlist); entry->Prev->next = entry->Next; entry->Next->prev = entry->Prev; histlist.count--; free(entry); } /* Removes the newest entry. */ void remove_last_entry(void) { if (histlist.count > 0) remove_entry(ashistentry(histlist.Newest)); } /* Renumbers all the entries in `histlist', starting from 1. */ void renumber_all_entries(void) { assert(!hist_lock); if (histlist.count > 0) { unsigned num = 0; for (histlink_T *l = histlist.Oldest; l != Histlist; l = l->next) ashistentry(l)->number = ++num; assert(num == histlist.count); } } /* Removes all entries in the history list. */ void clear_all_entries(void) { assert(!hist_lock); histlink_T *l = histlist.Oldest; while (l != Histlist) { histlink_T *next = l->next; free(ashistentry(l)); l = next; } histlist.Oldest = histlist.Newest = Histlist; histlist.count = 0; } /* Searches for the entry that has the specified `number'. * If there is such an entry in the history, the both members of the returned * `search_result_T' structure will be pointers to the entry. Otherwise, the * returned structure will contain pointers to the nearest neighbor entries. * (If there is not the neighbor in either direction, the pointer will be * `Histlist'.) */ struct search_result_T search_entry_by_number(unsigned number) { struct search_result_T result; if (histlist.count == 0) { result.prev = result.next = Histlist; return result; } unsigned oldestnum = ashistentry(histlist.Oldest)->number; unsigned nnewestnum = ashistentry(histlist.Newest)->number; unsigned nnumber = number; if (nnewestnum < oldestnum) { if (nnumber <= nnewestnum) nnumber += max_number; nnewestnum += max_number; } if (nnumber < oldestnum) { result.prev = Histlist; result.next = histlist.Oldest; return result; } else if (nnumber > nnewestnum) { result.prev = histlist.Newest; result.next = Histlist; return result; } histlink_T *l; if (2 * (nnumber - oldestnum) < nnewestnum - oldestnum) { /* search from the oldest */ l = histlist.Oldest; while (number < ashistentry(l)->number) l = l->next; while (number > ashistentry(l)->number) l = l->next; result.next = l; if (number != ashistentry(l)->number) l = l->prev; result.prev = l; } else { /* search from the newest */ l = histlist.Newest; while (number > ashistentry(l)->number) l = l->prev; while (number < ashistentry(l)->number) l = l->prev; result.prev = l; if (number != ashistentry(l)->number) l = l->next; result.next = l; } return result; } /* Returns the nth newest entry (or the oldest entry if `n' is too big). * Returns `Histlist' if `n' is zero or the history is empty. */ histlink_T *get_nth_newest_entry(unsigned n) { if (histlist.count <= n) return histlist.Oldest; histlink_T *l = Histlist; while (n-- > 0) l = l->prev; return l; } /* Searches for the newest entry whose value begins with the specified `prefix'. * Returns `Histlist' if not found. */ histlink_T *search_entry_by_prefix(const char *prefix) { histlink_T *l; for (l = histlist.Newest; l != Histlist; l = l->prev) if (matchstrprefix(ashistentry(l)->value, prefix) != NULL) break; return l; } /* Returns true iff `sr1' is newer than `sr2'. * For each search_result_T structure, the `prev' and `next' members must not be * both `Histlist'. */ bool search_result_is_newer( struct search_result_T sr1, struct search_result_T sr2) { if (sr1.prev == Histlist || sr2.next == Histlist) return false; if (sr1.next == Histlist || sr2.prev == Histlist) return true; return entry_is_newer(ashistentry(sr1.prev), ashistentry(sr2.prev)) || entry_is_newer(ashistentry(sr1.next), ashistentry(sr2.next)); } /* Returns true iff `e1' is newer than `e2'. */ bool entry_is_newer(const histentry_T *e1, const histentry_T *e2) { assert(histlist.count > 0); unsigned n1 = e1->number; unsigned n2 = e2->number; unsigned newest = ashistentry(histlist.Newest)->number; unsigned oldest = ashistentry(histlist.Oldest)->number; return (n1 <= newest && newest < oldest && oldest <= n2) || (n2 <= n1 && (oldest <= n2 || n1 <= newest)); } /********** Process ID list **********/ struct pidlist_T { size_t count; pid_t *pids; }; /* The process IDs of processes that share the history file. * The `pids' member may be null when the `count' member is zero. */ static struct pidlist_T histfilepids = { 0, NULL, }; /* Adds `pid' to `histfilepids'. `pid' must be positive. */ void add_histfile_pid(pid_t pid) { assert(pid > 0); for (size_t i = 0; i < histfilepids.count; i++) if (histfilepids.pids[i] == pid) return; /* don't add if already added */ histfilepids.pids = xrealloce(histfilepids.pids, histfilepids.count, 1, sizeof *histfilepids.pids); histfilepids.pids[histfilepids.count++] = pid; } /* If `pid' is non-zero, removes `pid' from `histfilepids'. * If `pid' is zero, removes process IDs of non-existent processes from * `histfilepids'. */ void remove_histfile_pid(pid_t pid) { for (size_t i = 0; i < histfilepids.count; ) { if (pid != 0 ? histfilepids.pids[i] == pid : !process_exists(histfilepids.pids[i])) { memmove(&histfilepids.pids[i], &histfilepids.pids[i + 1], (histfilepids.count - i - 1) * sizeof *histfilepids.pids); histfilepids.count--; histfilepids.pids = xreallocn(histfilepids.pids, histfilepids.count, sizeof *histfilepids.pids); } else { i++; } } } /* Clears `histfilepids'. */ void clear_histfile_pids(void) { free(histfilepids.pids); histfilepids = (struct pidlist_T) { 0, NULL, }; } /* Writes process IDs in `histfilepids' to the history file. */ /* This function does not return any error status. The caller should check * `ferror' for the file. */ void write_histfile_pids(void) { assert(histfile != NULL); for (size_t i = 0; i < histfilepids.count; i++) wprintf_histfile(L"p%jd\n", (intmax_t) histfilepids.pids[i]); histfilelines += histfilepids.count; } /********** History file functions **********/ /***** FORMAT OF THE HISTORY FILE ***** * * The first line of the history file has the following form: * #$# yash history v0 rXXX * where `XXX' is the revision number of the file. The revision number is * incremented each time the file is refreshed. * * The rest of the file consists of lines containing history data, each entry * per line. The type of entry is determined by the first character of the line: * 0-9 or A-F history entry * c history entry cancellation * d history entry deletion * p shell process addition/elimination info * others ignored line * * A history entry has the following form: * NNN:TTT COMMAND * where `NNN' is the entry number, `TTT' is the time of the command, and * `COMMAND' is the command. Both `NNN' and `TTT' are uppercase hexadecimal * non-negative numbers. `COMMAND' may contain any characters except newline and * null. * * A history entry cancellation consists of a single character `c'. It cancels * the previous history entry. * * A history entry deletion has the form: * dNNN * where `NNN' is the number of the entry to be removed (hexadecimal). * * A shell process addition/elimination is in the form: * pXXX * where `XXX' is the process id (decimal integer). For addition `XXX' is * positive and for elimination `XXX' is negative. */ /* Opens the history file. * Returns NULL on failure. */ FILE *open_histfile(void) { const wchar_t *vhistfile = getvar(L VAR_HISTFILE); if (vhistfile == NULL) return NULL; char *mbshistfile = malloc_wcstombs(vhistfile); if (mbshistfile == NULL) return NULL; int fd = open(mbshistfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); free(mbshistfile); if (fd < 0) return NULL; struct stat st; if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode) || (st.st_mode & (S_IRWXG | S_IRWXO))) { xclose(fd); return NULL; } fd = move_to_shellfd(fd); if (fd < 0) return NULL; FILE *f = fdopen(fd, "r+"); if (f == NULL) { remove_shellfd(fd); xclose(fd); } return f; } /* Locks the history file, which must have been open. * `type' must be one of `F_RDLCK', `F_WRLCK' and `F_UNLCK'. * If `type' is `F_UNLCK', the buffer for the history file is flushed before * unlocking the file. * When another process is holding a lock for the file, this process will be * blocked until the lock is freed. * Returns true iff successful. */ bool lock_histfile(short type) { if (type == F_UNLCK && histneedflush) { histneedflush = false; fflush(histfile); /* We only flush the history file after writing. POSIX doesn't define * the behavior of flush without writing. We don't use fseek instead of * fflush because fseek is less reliable than fflush. In some * implementations (including glibc), fseek doesn't flush the file. */ } struct flock flock = { .l_type = type, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, /* to the end of file */ }; int fd = fileno(histfile); int result; while ((result = fcntl(fd, F_SETLKW, &flock)) == -1 && errno == EINTR); return result != -1; } /* Reads one line from file `f'. * The line is appended to buffer `buf', which must have been initialized. * The terminating newline is not left in `buf'. * On failure, false is returned, in which case the contents of `buf' is * unspecified. * If there is no more line in the file, false is returned. * This function may ignore lines longer than LINE_MAX. */ bool read_line(FILE *restrict f, xwcsbuf_T *restrict buf) { bool accept_current_line = true; size_t initial_length = buf->length; for (;;) { if (try_read_line(f, buf)) { if (accept_current_line) return true; /* Read one more line and accept it. */ accept_current_line = true; } else { if (feof(f) || ferror(f)) return false; /* Now, the position of `f' is in the middle of a very long line * that should be ignored. */ accept_current_line = false; } wb_truncate(buf, initial_length); } } /* Reads one line from file `f'. * The line is appended to buffer `buf', which must have been initialized. * If a line was read successfully, true is returned. The terminating newline is * not left in `buf'. * The return value is false if: * * an error occurred (ferror()), * * the end of the input was reached (feof()), or * * the current line is too long (at least LINE_MAX characters), in which case * the position of `f' is left in the middle of the current line. * The contents of `buf' is unspecified if the return value is false. */ bool try_read_line(FILE *restrict f, xwcsbuf_T *restrict buf) { #if FGETWS_BROKEN wb_ensuremax(buf, LINE_MAX); #endif while (fgetws(&buf->contents[buf->length], buf->maxlength - buf->length + 1, f)) { size_t len = wcslen(&buf->contents[buf->length]); if (len == 0) return false; buf->length += len; if (buf->contents[buf->length - 1] == L'\n') { wb_truncate(buf, buf->length - 1); return true; } if (buf->length > LINE_MAX) return false; /* Too long line. Give up. */ wb_ensuremax(buf, add(buf->length, 80)); } return false; } /* Reads the signature of the history file (`histfile') and checks if it is a * valid signature. * If valid: * - the file is positioned just after the signature, * - the return value is the revision of the file (non-negative). * Otherwise: * - the file position is undefined, * - the return value is negative. */ /* The history file should be locked. */ long read_signature(void) { xwcsbuf_T buf; long rev = -1; const wchar_t *s; assert(histfile != NULL); rewind(histfile); if (!read_line(histfile, wb_init(&buf))) goto end; s = matchwcsprefix(buf.contents, L"#$# yash history v0 r"); if (s == NULL || !iswdigit(s[0])) goto end; if (!xwcstol(s, 10, &rev)) rev = -1; end: wb_destroy(&buf); return rev; } /* Reads history entries from the history file, which must have been open. * The file format is assumed a simple text, one entry per line. * The file is read from the current position. * The entries that were read from the file are appended to `histlist'. */ /* The file should be locked. */ /* This function does not return any error status. The caller should check * `ferror' and/or `feof' for the file. */ void read_history_raw(void) { xwcsbuf_T buf; assert(histfile != NULL); wb_init(&buf); while (read_line(histfile, &buf)) { char *line = malloc_wcstombs(buf.contents); if (line != NULL) { new_entry(next_history_number(), -1, line); free(line); } wb_clear(&buf); } wb_destroy(&buf); } /* Reads history entries from the history file. * The file is read from the current position. * The entries that were read from the file are appended to `histlist'. * `update_time' must be called before calling this function. */ /* The file should be locked. */ /* This function does not return any error status. The caller should check * `ferror' and/or `feof' for the file. */ void read_history(void) { xwcsbuf_T buf; assert(histfile != NULL); wb_init(&buf); while (read_line(histfile, &buf)) { histfilelines++; switch (buf.contents[0]) { case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'8': case L'9': case L'A': case L'B': case L'C': case L'D': case L'E': case L'F': parse_history_entry(buf.contents); break; case L'c': remove_last_entry(); break; case L'd': parse_removed_entry(buf.contents + 1); break; case L'p': parse_process_id(buf.contents + 1); break; } wb_clear(&buf); } wb_destroy(&buf); } void parse_history_entry(const wchar_t *line) { unsigned long num; time_t time; wchar_t *end; char *value; assert(iswxdigit(line[0])); errno = 0; num = wcstoul(line, &end, 0x10); if (errno || end[0] == L'\0' || num > max_number) return; if (end[0] == L':' && iswxdigit(end[1])) { unsigned long long t; errno = 0; t = wcstoull(&end[1], &end, 0x10); if (errno || end[0] == L'\0') time = -1; else if (t > (unsigned long long) now) time = now; else time = (time_t) t; } else { time = -1; } if (!iswspace(end[0])) return; value = malloc_wcstombs(&end[1]); if (value != NULL) { new_entry((unsigned) num, time, value); free(value); } } void parse_removed_entry(const wchar_t *numstr) { unsigned long num; wchar_t *end; if (histlist.count == 0) return; if (numstr[0] == L'\0') return; errno = 0; num = wcstoul(numstr, &end, 0x10); if (errno || (*end != L'\0' && !iswspace(*end))) return; if (num > max_number) return; struct search_result_T sr = search_entry_by_number((unsigned) num); if (sr.prev == sr.next) remove_entry(ashistentry(sr.prev)); } void parse_process_id(const wchar_t *numstr) { intmax_t num; wchar_t *end; if (numstr[0] == L'\0') return; errno = 0; num = wcstoimax(numstr, &end, 10); if (errno || (*end != L'\0' && !iswspace(*end))) return; if (num > 0) add_histfile_pid((pid_t) num); else if (num < 0) remove_histfile_pid((pid_t) -num); /* XXX: this cast and negation may be unsafe */ } /* Re-reads history from the history file. * Changes that have been made to the file by other shell processes are brought * into this shell's history. The current data in this shell's history may be * changed. * If `refresh' is true, this function may call `refresh_file'. * On failure, `histfile' is closed and set to NULL. * `update_time' must be called before calling this function. * After calling this function, `histfile' must not be read without * repositioning. */ /* The history file should be locked (F_WRLCK if `refresh' is true or F_RDLCK if * `refresh' is false). * This function must be called just before writing to the history file. */ void update_history(bool refresh) { bool posfail; fpos_t pos; long rev; if (histfile == NULL) return; assert(!hist_lock); #if WIO_BROKEN posfail = true; #else posfail = fgetpos(histfile, &pos); #endif rev = read_signature(); if (rev < 0) goto error; if (!posfail && rev == histfilerev) { /* The revision has not been changed. Just read new entries. */ fsetpos(histfile, &pos); read_history(); } else { /* The revision has been changed. Re-read everything. */ clear_all_entries(); clear_histfile_pids(); add_histfile_pid(shell_pid); histfilerev = rev; histfilelines = 0; read_history(); } if (ferror(histfile) || !feof(histfile)) goto error; if (refresh) maybe_refresh_file(); return; error: close_history_file(); } /* Refreshes the history file if it is time to do that. * `histfile' must not be NULL. */ void maybe_refresh_file(void) { assert(histfile != NULL); if (histfilelines > 20 && histfilelines / 2 >= histlist.count + histfilepids.count) { remove_histfile_pid(0); refresh_file(); } } /* Like `fwprintf(histfile, format, ...)', but the `histneedflush' flag is set. */ int wprintf_histfile(const wchar_t *format, ...) { va_list ap; int result; histneedflush = true; va_start(ap, format); result = vfwprintf(histfile, format, ap); va_end(ap); return result; } /* Writes the signature with an incremented revision number, after emptying the * file. */ /* This function does not return any error status. The caller should check * `ferror' for the file. */ void write_signature(void) { assert(histfile != NULL); rewind(histfile); while (ftruncate(fileno(histfile), 0) < 0 && errno == EINTR); if (histfilerev < 0 || histfilerev == LONG_MAX) histfilerev = 0; else histfilerev++; wprintf_histfile(L"#$# yash history v0 r%ld\n", histfilerev); histfilelines = 0; } /* Writes the specified entry to the history file. */ /* The file should be locked. */ /* This function does not return any error status. The caller should check * `ferror' for the file. */ void write_history_entry(const histentry_T *entry) { assert(histfile != NULL); /* don't print very long line */ if (xstrnlen(entry->value, LINE_MAX) >= LINE_MAX) return; if (entry->time >= 0) wprintf_histfile(L"%X:%lX %s\n", entry->number, (unsigned long) entry->time, entry->value); else wprintf_histfile(L"%X %s\n", entry->number, entry->value); histfilelines++; } /* Clears and rewrites the contents of the history file. * The file will have a new revision number. */ /* The file should be locked. */ /* This function does not return any error status. The caller should check * `ferror' for the file. */ void refresh_file(void) { write_signature(); write_histfile_pids(); for (const histlink_T *l = histlist.Oldest; l != Histlist; l = l->next) write_history_entry(ashistentry(l)); } /********** External functions **********/ /* Initializes history function if not yet initialized. * If the shell is not interactive, history is never initialized. */ void maybe_init_history(void) { static bool initialized = false; if (!is_interactive_now || initialized) return; initialized = true; /* set `histsize' */ const wchar_t *vhistsize = getvar(L VAR_HISTSIZE); if (vhistsize != NULL && vhistsize[0] != L'\0') { unsigned long size; if (xwcstoul(vhistsize, 10, &size)) set_histsize(size); } /* set `histrmdup' */ const wchar_t *vhistrmdup = getvar(L VAR_HISTRMDUP); if (vhistrmdup != NULL && vhistrmdup[0] != L'\0') { unsigned long rmdup; if (xwcstoul(vhistrmdup, 10, &rmdup)) histrmdup = (rmdup <= histsize) ? rmdup : histsize; } update_time(); /* open the history file and read it */ histfile = open_histfile(); if (histfile != NULL) { lock_histfile(F_WRLCK); histfilerev = read_signature(); if (histfilerev < 0) { rewind(histfile); read_history_raw(); goto refresh; } read_history(); if (ferror(histfile) || !feof(histfile)) { close_history_file(); return; } remove_histfile_pid(0); if (histfilepids.count == 0) { renumber_all_entries(); refresh: refresh_file(); } else { maybe_refresh_file(); } add_histfile_pid(shell_pid); wprintf_histfile(L"p%jd\n", (intmax_t) shell_pid); histfilelines++; lock_histfile(F_UNLCK); } } /* Closes the history file after writing info on the shell process that is * exiting. */ void finalize_history(void) { if (!is_interactive_now || histfile == NULL) return; hist_lock = false; lock_histfile(F_WRLCK); update_time(); update_history(true); if (histfile != NULL) { wprintf_histfile(L"p%jd\n", (intmax_t) -shell_pid); // histfilelines++; close_history_file(); } } /* Closes the history file if open. */ void close_history_file(void) { hist_lock = false; if (histfile == NULL) return; /* By closing the file descriptor for the history file, the file is * automatically unlocked. */ // lock_histfile(F_UNLCK); remove_shellfd(fileno(histfile)); fclose(histfile); histfile = NULL; } /* Calculates the number of the next new entry. */ unsigned next_history_number(void) { unsigned number; if (histlist.count == 0) { number = 1; } else { number = ashistentry(histlist.Newest)->number + 1; if (number > max_number) number = 1; } return number; } /* Adds the specified `line' to the history. * Must not be called while the history is locked. * If `line' contains newlines, `line' is separated into multiple entries. * Only lines that contain graph-class characters are added to the history. * If the `shopt_histspace' option is enabled and the `line' starts with a * blank, the `line' is not added. */ void add_history(const wchar_t *line) { if (shopt_histspace && iswblank(line[0])) return; maybe_init_history(); assert(!hist_lock); if (histfile != NULL) lock_histfile(F_WRLCK); update_time(); update_history(true); for (;;) { size_t len = wcscspn(line, L"\n"); add_history_line(line, len); line += len; if (line[0] == L'\0') break; line++; } if (histfile != NULL) lock_histfile(F_UNLCK); } /* Adds the specified `line' to the history. * If the line is longer than `maxlen' characters, only the first `maxlen' * characters are added. * The string added to the history must not contain newlines. * `histfile' must be locked and `update_time' and `update_history' must have * been called. * If the string does not contain any graph-class characters, it is not added * to the history. */ void add_history_line(const wchar_t *line, size_t maxlen) { /* Check if `line' contains `graph' characters */ for (size_t i = 0; ; i++) { if (i >= maxlen || line[i] == L'\0') return; assert(line[i] != L'\n'); if (iswgraph(line[i])) break; } char *mbsline = malloc_wcsntombs(line, maxlen); if (mbsline != NULL) { histentry_T *entry; remove_duplicates(mbsline); entry = new_entry(next_history_number(), now, mbsline); if (histfile != NULL) write_history_entry(entry); free(mbsline); } } /* Removes entries whose value is the same as `line' in the `histrmdup' newest * entries. * `histfile' must be locked and `update_history' must have been called. */ void remove_duplicates(const char *line) { histlink_T *l = histlist.Newest; for (unsigned i = histrmdup; i > 0 && l != Histlist; i--) { histlink_T *prev = l->prev; histentry_T *e = ashistentry(l); if (strcmp(e->value, line) == 0) { if (histfile != NULL) { wprintf_histfile(L"d%X\n", e->number); histfilelines++; } remove_entry(e); } l = prev; } } /* Returns the history entry that has the specified `number', or `Histlist' if * there is no such entry. */ const histlink_T *get_history_entry(unsigned number) { struct search_result_T sr = search_entry_by_number((unsigned) number); return (sr.prev == sr.next) ? sr.prev : Histlist; } #if YASH_ENABLE_LINEEDIT /* Calls `maybe_init_history' or `update_history' and locks the history. */ void start_using_history(void) { if (!hist_lock) { if (histfile != NULL) { lock_histfile(F_RDLCK); update_time(); update_history(false); if (histfile != NULL) lock_histfile(F_UNLCK); } else { maybe_init_history(); } hist_lock = true; } } /* Unlocks the history. */ void end_using_history(void) { hist_lock = false; } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-ins **********/ enum fcprinttype_T { FC_FULL, FC_NUMBERED, FC_UNNUMBERED, FC_RAW, }; static struct search_result_T fc_search_entry(const wchar_t *name, int number) __attribute__((nonnull)); static void fc_update_history(void); static void fc_remove_last_entry(void); static histlink_T *fc_search_entry_by_prefix(const wchar_t *prefix) __attribute__((nonnull)); static int fc_print_entries( FILE *f, const histentry_T *first, const histentry_T *last, bool reverse, enum fcprinttype_T type) __attribute__((nonnull)); static const char *fc_time_to_str(time_t time) __attribute__((pure)); static int fc_exec_entry(const histentry_T *entry, const wchar_t *old, const wchar_t *new, bool quiet) __attribute__((nonnull(1))); static int fc_edit_and_exec_entries( const histentry_T *first, const histentry_T *last, bool reverse, const wchar_t *editor, bool quiet) __attribute__((nonnull(1,2))); static void fc_read_history(FILE *f, bool quiet) __attribute__((nonnull)); static void history_clear_all(void); static int history_delete(const wchar_t *s) __attribute__((nonnull)); static int history_read(const wchar_t *s) __attribute__((nonnull)); static int history_write(const wchar_t *s) __attribute__((nonnull)); static void history_refresh_file(void); const struct xgetopt_T fc_options[] = { { L'e', L"editor", OPTARG_REQUIRED, true, NULL, }, { L'l', L"list", OPTARG_NONE, true, NULL, }, { L'n', L"no-numbers", OPTARG_NONE, true, NULL, }, { L'q', L"quiet", OPTARG_NONE, false, NULL, }, { L'r', L"reverse", OPTARG_NONE, true, NULL, }, { L's', L"silent", OPTARG_NONE, true, NULL, }, { L'v', L"verbose", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "fc" built-in, which accepts the following options: * -e: specify the editor to edit history * -l: list history * -n: don't print entry numbers * -r: reverse entry order * -s: execute without editing * -v: print time for each entry */ int fc_builtin(int argc, void **argv) { const wchar_t *editor = NULL; bool list = false, quiet = false, rev = false, silent = false; enum fcprinttype_T ptype = FC_NUMBERED; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, fc_options, XGETOPT_DIGIT))) { switch (opt->shortopt) { case L'e': editor = xoptarg; break; case L'l': list = true; break; case L'n': ptype = FC_UNNUMBERED; break; case L'q': quiet = true; break; case L'r': rev = true; break; case L's': silent = true; break; case L'v': ptype = FC_FULL; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } /* parse */ const wchar_t *old = NULL, *new = NULL; if (silent && xoptind < argc) { wchar_t *eq = wcschr(ARGV(xoptind), L'='); if (eq != NULL) { eq[0] = L'\0'; old = ARGV(xoptind); new = &eq[1]; xoptind++; } } /* error checks */ if (editor && list) return mutually_exclusive_option_error(L'e', L'l'); if (editor && silent) return mutually_exclusive_option_error(L'e', L's'); if (list && quiet) return mutually_exclusive_option_error(L'l', L'q'); if (list && silent) return mutually_exclusive_option_error(L'l', L's'); if (rev && silent) return mutually_exclusive_option_error(L'r', L's'); if (ptype != FC_NUMBERED && !list) { xerror(0, Ngt("the -n or -v option must be used with the -l option")); return Exit_ERROR; } if (!validate_operand_count(argc - xoptind, 0, silent ? 1 : 2)) return Exit_ERROR; if (hist_lock) { xerror(0, Ngt("cannot be used during line-editing")); return Exit_FAILURE; } maybe_init_history(); if (list) { fc_update_history(); } else { /* remove the entry for this "fc" command */ fc_remove_last_entry(); } if (histlist.count == 0) { if (list) { return Exit_SUCCESS; } else { xerror(0, Ngt("the command history is empty")); return Exit_FAILURE; } } /* parse */ const wchar_t *vfirst = (xoptind < argc) ? ARGV(xoptind++) : NULL; int nfirst; struct search_result_T lfirst; if (vfirst != NULL) { if (!xwcstoi(vfirst, 10, &nfirst) /* || nfirst == 0 */) nfirst = 0; } else { nfirst = list ? -16 : -1; } lfirst = fc_search_entry(vfirst, nfirst); if (lfirst.prev == Histlist && lfirst.next == Histlist) return Exit_FAILURE; /* main part of our work with the -s flag */ if (silent) { if (lfirst.prev != lfirst.next) { assert(vfirst != NULL); xerror(0, Ngt("no such history entry `%ls'"), vfirst); return Exit_FAILURE; } return fc_exec_entry(ashistentry(lfirst.prev), old, new, quiet); } /* parse */ const wchar_t *vlast = (xoptind < argc) ? ARGV(xoptind++) : NULL; int nlast; struct search_result_T llast; if (vlast != NULL) { if (!xwcstoi(vlast, 10, &nlast) /* || nlast == 0 */) nlast = 0; } else if (list) { nlast = -1; } else { llast = lfirst; goto check_rev; } llast = fc_search_entry(vlast, nlast); if (llast.prev == Histlist && llast.next == Histlist) return Exit_FAILURE; check_rev: if (search_result_is_newer(lfirst, llast)) { struct search_result_T temp = lfirst; lfirst = llast; llast = temp; rev = !rev; } const histentry_T *efirst = ashistentry(lfirst.next == Histlist ? lfirst.prev : lfirst.next); const histentry_T *elast = ashistentry(llast.prev == Histlist ? llast.next : llast.prev); if (list) return fc_print_entries(stdout, efirst, elast, rev, ptype); else return fc_edit_and_exec_entries(efirst, elast, rev, editor, quiet); } /* Searches for the specified entry. * The `prev' and `next' members of the returned structure will be `Histlist' * if the prefix search fails. */ struct search_result_T fc_search_entry(const wchar_t *name, int number) { struct search_result_T result; if (number == 0) { result.prev = result.next = fc_search_entry_by_prefix(name); } else if (number > 0) { result = search_entry_by_number((unsigned) number); assert(result.prev != Histlist || result.next != Histlist); } else { number = -number; result.next = get_nth_newest_entry((unsigned) number); if ((unsigned) number > histlist.count) result.prev = Histlist; else result.prev = result.next; assert(result.prev != Histlist || result.next != Histlist); } return result; } void fc_update_history(void) { if (histfile != NULL) { lock_histfile(F_RDLCK); update_time(); update_history(false); if (histfile != NULL) lock_histfile(F_UNLCK); } } void fc_remove_last_entry(void) { if (histfile != NULL) { lock_histfile(F_WRLCK); update_time(); update_history(true); remove_last_entry(); if (histfile != NULL) { wprintf_histfile(L"c\n"); histfilelines++; lock_histfile(F_UNLCK); } } else { remove_last_entry(); } } /* Finds the newest entry that begins with the specified prefix. * Prints an error message and returns `Histlist' if not found. */ histlink_T *fc_search_entry_by_prefix(const wchar_t *prefix) { char *s = malloc_wcstombs(prefix); if (s == NULL) return Histlist; histlink_T *l = search_entry_by_prefix(s); free(s); if (l == Histlist) xerror(0, Ngt("no such history entry beginning with `%ls'"), prefix); return l; } /* Print history entries between `first' and `last'. * Only byte-oriented output functions are used for `f'. * An error message is printed on error. */ int fc_print_entries( FILE *f, const histentry_T *first, const histentry_T *last, bool reverse, enum fcprinttype_T type) { const histentry_T *start, *end, *e; if (!reverse) start = first, end = last; else start = last, end = first; e = start; for (;;) { int r; switch (type) { case FC_FULL: r = fprintf(f, "%u\t%s\t%s\n", e->number, fc_time_to_str(e->time), e->value); break; case FC_NUMBERED: r = fprintf(f, "%u\t%s\n", e->number, e->value); break; case FC_UNNUMBERED: r = fprintf(f, "\t%s\n", e->value); break; case FC_RAW: r = fprintf(f, "%s\n", e->value); break; default: assert(false); } if (r < 0) { xerror(errno, Ngt("cannot print to the standard output")); return Exit_FAILURE; } if (e == end) break; e = ashistentry(!reverse ? e->Next : e->Prev); } return Exit_SUCCESS; } /* Converts time to a string. * The return value is valid until the next call to this function. */ const char *fc_time_to_str(time_t time) { static char s[80]; if (time >= 0) { size_t size = strftime(s, sizeof s, "%c", localtime(&time)); if (size > 0) return s; } s[0] = '?', s[1] = '\0'; return s; } /* Executes the value of `entry'. * If `old' is not NULL, the first occurrence of `old' in the command is * replaced with `new' before execution (but the entry's value is unchanged). * If `quiet' is false, prints the command before execution. */ int fc_exec_entry(const histentry_T *entry, const wchar_t *old, const wchar_t *new, bool quiet) { wchar_t *code = malloc_mbstowcs(entry->value); if (code == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return Exit_ERROR; } if (old != NULL) { xwcsbuf_T buf; wchar_t *p; wb_initwith(&buf, code); p = wcsstr(buf.contents, old); if (p != NULL) wb_replace(&buf, p - buf.contents, wcslen(old), new, SIZE_MAX); code = wb_towcs(&buf); } add_history(code); if (!quiet) printf("%ls\n", code); exec_wcs(code, "fc", false); free(code); return laststatus; } /* Invokes the editor to let the user edit the history entries between `first' * and `last' and executes the edited entries. */ int fc_edit_and_exec_entries( const histentry_T *first, const histentry_T *last, bool reverse, const wchar_t *editor, bool quiet) { char *temp; int fd; FILE *f; pid_t cpid; int savelaststatus; fd = create_temporary_file(&temp, ".sh", S_IRUSR | S_IWUSR); if (fd < 0) { xerror(errno, Ngt("cannot create a temporary file to edit history")); goto error1; } f = fdopen(fd, "w"); if (f == NULL) { xerror(errno, Ngt("cannot open temporary file `%s'"), temp); xclose(fd); goto error2; } savelaststatus = laststatus; cpid = fork_and_reset(0, true, 0); if (cpid < 0) { // fork failed xerror(0, Ngt("cannot invoke the editor to edit history")); fclose(f); if (unlink(temp) < 0) xerror(errno, Ngt("failed to remove temporary file `%s'"), temp); error2: free(temp); error1: return Exit_FAILURE; } else if (cpid > 0) { // parent process fclose(f); wchar_t **namep = wait_for_child( cpid, doing_job_control_now ? cpid : 0, doing_job_control_now); if (namep != NULL) { *namep = malloc_wprintf(L"%ls %s", editor ? editor : L"${FCEDIT:-ed}", temp); } if (laststatus != Exit_SUCCESS) { xerror(0, Ngt("the editor returned a non-zero exit status")); fd = -1; } else { fd = move_to_shellfd(open(temp, O_RDONLY)); if (fd < 0) xerror(errno, Ngt("cannot read commands from file `%s'"), temp); } if (unlink(temp) < 0) xerror(errno, Ngt("failed to remove temporary file `%s'"), temp); free(temp); if (fd < 0) return Exit_FAILURE; f = fdopen(fd, "r"); fc_read_history(f, quiet); lseek(fd, 0, SEEK_SET); laststatus = savelaststatus; exec_input(fd, "fc", XIO_SUBST_ALIAS); remove_shellfd(fd); fclose(f); return laststatus; } else { // child process fc_print_entries(f, first, last, reverse, FC_RAW); fclose(f); wchar_t *command = malloc_wprintf(L"%ls %s", (editor != NULL) ? editor : L"${FCEDIT:-ed}", temp); free(temp); exec_wcs(command, "fc", true); #ifndef NDEBUG free(command); #endif assert(false); } } void fc_read_history(FILE *f, bool quiet) { xwcsbuf_T buf; if (histfile != NULL) lock_histfile(F_WRLCK); update_time(); update_history(false); wb_init(&buf); while (read_line(f, &buf)) { if (!quiet) printf("%ls\n", buf.contents); add_history_line(buf.contents, buf.length); wb_clear(&buf); } wb_destroy(&buf); if (histfile != NULL) { maybe_refresh_file(); lock_histfile(F_UNLCK); } } #if YASH_ENABLE_HELP const char fc_help[] = Ngt( "list or re-execute command history" ); const char fc_syntax[] = Ngt( "\tfc [-qr] [-e editor] [first [last]]\n" "\tfc -s [-q] [old=new] [first]\n" "\tfc -l [-nrv] [first [last]]\n" ); #endif /* Options for the "history" built-in. */ const struct xgetopt_T history_options[] = { { L'c', L"clear", OPTARG_NONE, true, NULL, }, { L'd', L"delete", OPTARG_REQUIRED, true, NULL, }, { L'r', L"read", OPTARG_REQUIRED, true, NULL, }, { L's', L"set", OPTARG_REQUIRED, true, NULL, }, { L'w', L"write", OPTARG_REQUIRED, true, NULL, }, { L'F', L"flush-file", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "history" built-in, which accepts the following options: * -c: clear whole history * -d: remove history entry * -r: read history from a file * -s: add history entry * -w: write history into a file * -F: flush history file */ int history_builtin(int argc, void **argv) { if (hist_lock) { xerror(0, Ngt("cannot be used during line-editing")); return Exit_FAILURE; } maybe_init_history(); bool hasoption = false, removedthis = false; int result = Exit_SUCCESS; /* process options */ const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, history_options, 0)) != NULL) { hasoption = true; switch (opt->shortopt) { case L'c': history_clear_all(); break; case L'd': result = history_delete(xoptarg); break; case L'r': result = history_read(xoptarg); break; case L's': if (!removedthis) { fc_remove_last_entry(); removedthis = true; } add_history(xoptarg); break; case L'w': result = history_write(xoptarg); break; case L'F': history_refresh_file(); break; #if YASH_ENABLE_HELP case L'-': result = print_builtin_help(ARGV(0)); break; #endif default: return Exit_ERROR; } if (result != Exit_SUCCESS) return result; } /* print history */ int count; if (xoptind < argc) { if (!validate_operand_count(argc - xoptind, 0, 1)) return Exit_ERROR; if (!xwcstoi(ARGV(xoptind), 10, &count)) { xerror(errno, Ngt("`%ls' is not a valid integer"), ARGV(xoptind)); return Exit_ERROR; } if (count <= 0) return Exit_SUCCESS; } else if (!hasoption) { count = INT_MAX; } else { return Exit_SUCCESS; } fc_update_history(); histlink_T *start = get_nth_newest_entry((unsigned) count); if (start == Histlist) return Exit_SUCCESS; return fc_print_entries(stdout, ashistentry(start), ashistentry(histlist.Newest), false, FC_NUMBERED); } /* Clears all the history. */ void history_clear_all(void) { if (histfile != NULL) { lock_histfile(F_WRLCK); update_history(false); } clear_all_entries(); if (histfile != NULL) { refresh_file(); lock_histfile(F_UNLCK); } } /* Deletes a history entry specified by the argument string. */ int history_delete(const wchar_t *s) { int n; histlink_T *l; if (histfile != NULL) { lock_histfile(F_WRLCK); update_time(); update_history(true); } if (!xwcstoi(s, 10, &n) || n == 0) { l = fc_search_entry_by_prefix(s); } else { if (n >= 0) { struct search_result_T sr = search_entry_by_number((unsigned) n); l = (sr.prev == sr.next) ? sr.prev : Histlist; } else { if (n != INT_MIN) n = -n; else n = INT_MAX; l = get_nth_newest_entry((unsigned) n); } if (l == Histlist) xerror(0, Ngt("no such history entry `%ls'"), s); } if (l != Histlist) { histentry_T *e = ashistentry(l); if (histfile != NULL) { wprintf_histfile(L"d%X\n", e->number); histfilelines++; } remove_entry(e); } if (histfile != NULL) lock_histfile(F_UNLCK); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Reads history from the specified file. */ int history_read(const wchar_t *s) { FILE *f; /* The `fc_read_history' function assumes the argument stream wide-oriented. * We don't pass `stdin' to `fc_read_history' so that it remains * non-oriented. */ if (wcscmp(s, L"-") == 0) { int fd = copy_as_shellfd(STDIN_FILENO); if (fd < 0) goto error; f = fdopen(fd, "r"); } else { char *mbsfilename = malloc_wcstombs(s); if (mbsfilename == NULL) goto error; f = fopen(mbsfilename, "r"); free(mbsfilename); } if (f == NULL) goto error; fc_read_history(f, true); bool error = ferror(f); error |= fclose(f); if (!error) return Exit_SUCCESS; error: xerror(0, Ngt("cannot read history from file `%ls'"), s); return Exit_FAILURE; } /* Writes history into the specified file. */ int history_write(const wchar_t *s) { FILE *f; fc_update_history(); if (histlist.count == 0) return Exit_SUCCESS; if (wcscmp(s, L"-") == 0) { f = stdout; } else { char *mbsfilename = malloc_wcstombs(s); if (mbsfilename == NULL) goto error; f = fopen(mbsfilename, "w"); free(mbsfilename); if (f == NULL) goto error; } int result = fc_print_entries(f, ashistentry(histlist.Oldest), ashistentry(histlist.Newest), false, FC_RAW); if (f != stdout) if (fclose(f) != 0) goto error; return result; error: xerror(0, Ngt("cannot write history to file `%ls'"), s); return Exit_FAILURE; } /* Refreshes the history file. */ void history_refresh_file(void) { if (histfile != NULL) { lock_histfile(F_WRLCK); update_time(); update_history(false); if (histfile != NULL) { remove_histfile_pid(0); refresh_file(); lock_histfile(F_UNLCK); } } } #if YASH_ENABLE_HELP const char history_help[] = Ngt( "manage command history" ); const char history_syntax[] = Ngt( "\thistory [-cF] [-d entry] [-s command] [-r file] [-w file] [count]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/history.h000066400000000000000000000062471354143602500143020ustar00rootroot00000000000000/* Yash: yet another shell */ /* history.h: command history management */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_HISTORY_H #define YASH_HISTORY_H #include #include #include "xgetopt.h" /* The structure type of doubly-linked list node. */ typedef struct histlink_T { struct histlink_T *prev, *next; } histlink_T; /* `prev' and `next' are always non-NULL: the newest entry's `next' and the * oldest entry's `prev' point to `histlist'. */ /* The structure type of history entries. */ typedef struct histentry_T { histlink_T link; unsigned number; time_t time; char value[]; } histentry_T; #define Prev link.prev #define Next link.next /* The value is stored as a multibyte string rather than a wide string to save * memory space. The value must not contain newlines. */ /* Basically the `number' is increased for each entry, but the numbers are * limited to some extent. If the number exceeds the limit, it is wrapped * around to 1, so a newer entry may have a smaller number than an older entry. * The limit is no less than $HISTSIZE, so all the entries have different * numbers anyway. */ /* When the time is unknown, `time' is -1. */ /* The structure type of the history list. */ typedef struct histlist_T { histlink_T link; unsigned count; } histlist_T; #define Newest link.prev #define Oldest link.next extern histlist_T histlist; #define Histlist (&histlist.link) /* Casts a pointer to `histlink_T' into a pointer to `histentry_T'. */ static inline histentry_T *ashistentry(const histlink_T *link) { #ifdef assert assert(link != &histlist.link); #endif return (histentry_T *) link; } extern unsigned next_history_number(void) __attribute__((pure)); extern void maybe_init_history(void); extern void finalize_history(void); extern void close_history_file(void); extern void add_history(const wchar_t *line) __attribute__((nonnull)); const histlink_T *get_history_entry(unsigned number) __attribute__((pure)); #if YASH_ENABLE_LINEEDIT extern void start_using_history(void); extern void end_using_history(void); #endif extern int fc_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char fc_help[], fc_syntax[]; #endif extern const struct xgetopt_T fc_options[]; extern int history_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char history_help[], history_syntax[]; #endif extern const struct xgetopt_T history_options[]; #endif /* YASH_HISTORY_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/input.c000066400000000000000000000310661354143602500137300ustar00rootroot00000000000000/* Yash: yet another shell */ /* input.c: functions for input of command line */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "input.h" #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "exec.h" #include "expand.h" #if YASH_ENABLE_HISTORY # include "history.h" #endif #include "job.h" #include "mail.h" #include "option.h" #include "parser.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "yash.h" #if YASH_ENABLE_LINEEDIT # include "lineedit/display.h" # include "lineedit/lineedit.h" #endif static wchar_t *expand_prompt_variable(wchar_t num, wchar_t suffix) __attribute__((malloc,warn_unused_result)); static const wchar_t *get_prompt_variable(wchar_t num, wchar_t suffix) __attribute__((pure)); static wchar_t *expand_ps1_posix(wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); static inline wchar_t get_euid_marker(void) __attribute__((pure)); /* An input function that inputs from a wide string. * `inputinfo' must be a pointer to a `struct input_wcs_info_T'. * Reads the next line from `inputinfo->src' and appends it to buffer `buf'. * `inputinfo->src' is assigned NULL if there are no more lines remaining. */ inputresult_T input_wcs(struct xwcsbuf_T *buf, void *inputinfo) { struct input_wcs_info_T *info = inputinfo; const wchar_t *src = info->src; if (src == NULL) return INPUT_EOF; const wchar_t *newlinep = wcschr(src, L'\n'); if (newlinep != NULL) { const wchar_t *nextlinep = &newlinep[1]; wb_ncat_force(buf, src, nextlinep - src); info->src = nextlinep; return INPUT_OK; } bool isempty = (src[0] == L'\0'); wb_cat(buf, src); info->src = NULL; return isempty ? INPUT_EOF : INPUT_OK; } /* An input function that reads input from a file stream. * `inputinfo' is a pointer to a `struct input_file_info_T'. * Reads one line from `inputinfo->fd' and appends it to the buffer. */ inputresult_T input_file(struct xwcsbuf_T *buf, void *inputinfo) { return read_input(buf, inputinfo, true); } /* Reads one line from file descriptor `info->fd' and appends it to `buf'. * If `trap' is true, traps are handled while reading and the `sigint_received' * flag is cleared when this function returns. * Returns: * INPUT_OK if at least one character was appended * INPUT_EOF if reached the end of file without reading any characters * INPUT_ERROR if an error occurred before reading any characters */ inputresult_T read_input( xwcsbuf_T *buf, struct input_file_info_T *info, bool trap) { size_t initlen = buf->length; inputresult_T status = INPUT_EOF; for (;;) { if (info->bufpos >= info->bufmax) { read_input: /* if there's nothing in the buffer, read the next input */ switch (wait_for_input(info->fd, trap, -1)) { case W_READY: break; case W_TIMED_OUT: assert(false); case W_INTERRUPTED: // Ignore interruption and continue reading, because: // 1) POSIX does not require to handle interruption, and // 2) the buffer for canonical-mode editing cannot be // controlled from the shell. goto read_input; case W_ERROR: status = INPUT_ERROR; goto end; } ssize_t readcount = read(info->fd, info->buf, info->bufsize); if (readcount < 0) switch (errno) { case EINTR: case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif goto read_input; /* try again */ default: goto error; } else if (readcount == 0) { goto end; } info->bufpos = 0; info->bufmax = readcount; } /* convert bytes in `info->buf' into a wide character and * append it to `buf' */ wb_ensuremax(buf, add(buf->length, 1)); assert(info->bufpos < info->bufmax); size_t convcount = mbrtowc(&buf->contents[buf->length], &info->buf[info->bufpos], info->bufmax - info->bufpos, &info->state); switch (convcount) { case 0: /* read null character */ goto end; case (size_t) -1: /* not a valid character */ goto error; case (size_t) -2: /* needs more input */ goto read_input; default: info->bufpos += convcount; buf->contents[++buf->length] = L'\0'; if (buf->contents[buf->length - 1] == L'\n') goto end; break; } } error: xerror(errno, Ngt("cannot read input")); status = INPUT_ERROR; end: if (initlen != buf->length) return INPUT_OK; else return status; } /* An input function that prints a prompt and reads input. * `inputinfo' is a pointer to a `struct input_interactive_info'. * `inputinfo->type' must be either 1 or 2, which specifies the prompt type. * For example, $PS1 is printed if `inputinfo->type' is 1. * If `inputinfo->type' is 1, this function changes it to 2 after a successful * read. * The line is added to the history. */ inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) { struct input_interactive_info_T *info = inputinfo; struct promptset_T prompt; if (info->prompttype == 1) { if (!posixly_correct) exec_variable_as_auxiliary_(VAR_PROMPT_COMMAND); check_mail(); } prompt = get_prompt(info->prompttype); if (do_job_control) print_job_status_all(); /* Note: no commands must be executed between `print_job_status_all' here * and `le_readline', or the "notifyle" option won't work. More precisely, * `handle_sigchld' must not be called from any other function until it is * called from `wait_for_input' during the line-editing. */ #if YASH_ENABLE_LINEEDIT /* read a line using line editing */ if (info->fileinfo->fd == STDIN_FILENO && shopt_lineedit != SHOPT_NOLINEEDIT) { wchar_t *line; inputresult_T result; result = le_readline(prompt, true, &line); if (result != INPUT_ERROR) { free_prompt(prompt); if (result == INPUT_OK) { if (info->prompttype == 1) info->prompttype = 2; #if YASH_ENABLE_HISTORY add_history(line); #endif wb_catfree(buf, line); } return result; } } #endif /* YASH_ENABLE_LINEEDIT */ /* read a line without line editing */ print_prompt(prompt.main); print_prompt(prompt.styler); if (info->prompttype == 1) info->prompttype = 2; int result; #if YASH_ENABLE_HISTORY size_t oldlen = buf->length; #endif result = input_file(buf, info->fileinfo); print_prompt(PROMPT_RESET); free_prompt(prompt); #if YASH_ENABLE_HISTORY add_history(buf->contents + oldlen); #endif return result; } /* Returns the prompt string, possibly containing backslash escapes. * `type' must be 1, 2 or 4. * This function never fails: A newly malloced string is always returned. * `save_parse_state' must be called before calling this function because this * function calls `parse_string'. */ struct promptset_T get_prompt(int type) { struct promptset_T result; wchar_t num; switch (type) { case 1: num = L'1'; break; case 2: num = L'2'; break; case 4: num = L'4'; break; default: assert(false); } wchar_t *prompt = expand_prompt_variable(num, L'\0'); if (posixly_correct) { if (type == 1) result.main = expand_ps1_posix(prompt); else result.main = escapefree(prompt, L"\\"); result.right = xwcsdup(L""); result.styler = xwcsdup(L""); } else { result.main = prompt; result.right = expand_prompt_variable(num, L'R'); result.styler = expand_prompt_variable(num, L'S'); } return result; } /* Expands the result of `get_prompt_variable' for a prompt. * The result is a newly-malloced string. */ wchar_t *expand_prompt_variable(wchar_t num, wchar_t suffix) { const wchar_t *var = get_prompt_variable(num, suffix); wchar_t *expanded = parse_and_expand_string(var, gt("prompt"), false); return expanded != NULL ? expanded : xwcsdup(L""); } /* Returns the value of the variable "YASH_PSxy", where x is `num' and y is * `suffix'. If it is unset or the shell is in the POSIXly-correct mode, returns * the value of "PSxy". If it is also unset, returns an empty string. */ const wchar_t *get_prompt_variable(wchar_t num, wchar_t suffix) { wchar_t name[] = L"YASH_PS\0\0"; name[7] = num; name[8] = suffix; const wchar_t *value; if (!posixly_correct) { value = getvar(name); if (value != NULL) return value; } value = getvar(&name[5]); if (value != NULL) return value; return L""; } /* Expands the contents of the PS1 variable in the posixly correct way. * The argument is `free'd in this function. * The return value must be `free'd by the caller. */ /* In this function, "!" is replaced with "\!", "!!" with "!", and "\" with * "\\". */ wchar_t *expand_ps1_posix(wchar_t *s) { xwcsbuf_T buf; wb_init(&buf); for (size_t i = 0; s[i] != L'\0'; i++) { if (s[i] == L'\\') { wb_wccat(&buf, L'\\'); wb_wccat(&buf, L'\\'); } else if (s[i] == L'!') { if (s[i + 1] == L'!') { wb_wccat(&buf, L'!'); i++; } else { wb_wccat(&buf, L'\\'); wb_wccat(&buf, L'!'); } } else { wb_wccat(&buf, s[i]); } } free(s); return wb_towcs(&buf); } /* Prints the specified prompt string to the standard error. * If the argument is NULL, the "sgr0" terminfo capability is printed, which * resets the terminal's font style. * * Escape sequences are handled: * \a a bell character: L'\a' (L'\07') * \e an escape code: L'\033' * \j the number of jobs * \n newline: L'\n' * \r carriage return: L'\r' * \! next history number * \$ L'#' if the effective user ID is 0, L'$' otherwise * \\ a backslash * * If line-editing is enabled, the followings are also available: * \fX change color * \[ start of substring not to be counted as printable characters * \] end of substring not to be counted as printable characters * "X" in "\fX" can be any number of flags from the following: * (foreground color) * k (black) r (red) g (green) y (yellow) * b (blue) m (magenta) c (cyan) w (white) * (background color) * K R G Y B M C W * t (brighter color: used just after a color flag above) * d (default foreground/background color) * D (default foreground/background color and style) * s (standout) * u (underline) * v (reverse) * n (blink) * i (dim) * o (bold) * x (invisible) * . (end: omittable) * These are ignored in this function. */ void print_prompt(const wchar_t *s) { #if YASH_ENABLE_LINEEDIT if (le_try_print_prompt(s)) return; #endif xwcsbuf_T buf; wb_init(&buf); while (*s != L'\0') { if (*s != L'\\') { wb_wccat(&buf, *s); } else switch (*++s) { default: wb_wccat(&buf, *s); break; case L'\0': wb_wccat(&buf, L'\\'); goto done; // case L'\\': wb_wccat(&buf, L'\\'); break; case L'a': wb_wccat(&buf, L'\a'); break; case L'e': wb_wccat(&buf, L'\033'); break; case L'n': wb_wccat(&buf, L'\n'); break; case L'r': wb_wccat(&buf, L'\r'); break; case L'$': wb_wccat(&buf, get_euid_marker()); break; case L'j': wb_wprintf(&buf, L"%zu", job_count()); break; #if YASH_ENABLE_HISTORY case L'!': wb_wprintf(&buf, L"%u", next_history_number()); break; #endif case L'[': case L']': break; case L'f': while (iswalnum(*++s)); if (*s == L'.') s++; continue; } s++; } done: fprintf(stderr, "%ls", buf.contents); fflush(stderr); wb_destroy(&buf); } wchar_t get_euid_marker(void) { return geteuid() == 0 ? L'#' : L'$'; } /* Unsets O_NONBLOCK flag of the specified file descriptor. * If `fd' is negative, does nothing. * Returns true if successful. On error, `errno' is set and false is returned.*/ bool unset_nonblocking(int fd) { if (fd >= 0) { int flags = fcntl(fd, F_GETFL); if (flags < 0) return false; if (flags & O_NONBLOCK) return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) != -1; } return true; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/input.h000066400000000000000000000067441354143602500137420ustar00rootroot00000000000000/* Yash: yet another shell */ /* input.h: functions for input of command line */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_INPUT_H #define YASH_INPUT_H #include #include struct promptset_T { wchar_t *main, *right, *styler; }; #define PROMPT_RESET L"\\fD" extern struct promptset_T get_prompt(int type); static inline void free_prompt(struct promptset_T prompt); extern void print_prompt(const wchar_t *s) __attribute__((nonnull)); extern _Bool unset_nonblocking(int fd); /* Frees the specified prompt set. */ void free_prompt(struct promptset_T prompt) { free(prompt.main); free(prompt.right); free(prompt.styler); } /* The result type of `inputfunc_T'. */ typedef enum inputresult_T { INPUT_OK, /* A line was read successfully. */ INPUT_EOF, /* The end of file was reached. */ INPUT_INTERRUPTED, /* SIGINT was received (interactive shell only) */ INPUT_ERROR, /* Other error was encountered. */ } inputresult_T; struct xwcsbuf_T; struct input_file_info_T; extern inputresult_T read_input( struct xwcsbuf_T *buf, struct input_file_info_T *info, _Bool trap) __attribute__((nonnull)); /* The type of input functions. * An input function reads input and appends it to buffer `buf'. * Input is done line-wise: the buffer contents are always terminated by a * newline character (L'\n') except when the end of file is reached and the last * line does not have a newline. * An input function should not read more than one line at a time, as commands * (which may contain alias definitions) should be executed as soon as possible, * before the next line is parsed. * The result is indicated by a value of the `inputresult_T' type. If the return * value is other than INPUT_OK, the buffer is unchanged. * The input function may be called even after it returned a value other than * INPUT_OK. */ typedef inputresult_T inputfunc_T(struct xwcsbuf_T *buf, void *inputinfo); /* input functions */ extern inputresult_T input_wcs(struct xwcsbuf_T *buf, void *inputinfo) __attribute__((nonnull)); extern inputresult_T input_file(struct xwcsbuf_T *buf, void *inputinfo) __attribute__((nonnull)); extern inputresult_T input_interactive(struct xwcsbuf_T *buf, void *inputinfo) __attribute__((nonnull)); /* to be used as `inputinfo' for `input_wcs' */ struct input_wcs_info_T { const wchar_t *src; /* the input source code */ }; /* to be used as `inputinfo' for `input_file' */ struct input_file_info_T { int fd; mbstate_t state; size_t bufpos, bufmax, bufsize; char buf[]; }; /* `bufsize' is the size of `buf', which must be at least one byte. */ /* to be used as `inputinfo' for `input_interactive' */ struct input_interactive_info_T { struct input_file_info_T *fileinfo; int prompttype; }; #endif /* YASH_INPUT_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/install-sh000077500000000000000000000324671354143602500144370ustar00rootroot00000000000000# install - install a program, script, or datafile scriptversion=2006-12-25.00-rev2 # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. nl=' ' IFS=" "" $nl" # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit=${DOITPROG-} if test -z "$doit"; then doit_exec=exec else doit_exec=$doit fi # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_glob='?' initialize_posix_glob=' test "$posix_glob" != "?" || { if (set -f) 2>/dev/null; then posix_glob= else posix_glob=: fi } ' posix_mkdir= # Desired mode of installed file. mode=0755 chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false no_target_directory= usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve the last data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -s $stripprog installed files. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *' '* | *' '* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -s) stripcmd=$stripprog;; -t) dst_arg=$2 shift;; -T) no_target_directory=true;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call `install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then trap '(exit $?); exit' HUP INT PIPE TERM # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names starting with `-'. case $src in -*) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # Protect names starting with `-'. case $dst in -*) dst=./$dst;; esac # If destination is a directory, append the input filename; won't work # if double slashes aren't ignored. if test -d "$dst"; then if test -n "$no_target_directory"; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dst=$dstdir/`basename "$src"` dstdir_status=0 else # Prefer dirname, but fall back on a substitute if dirname fails. dstdir=` (dirname "$dst") 2>/dev/null || expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$dst" : 'X\(//\)[^/]' \| \ X"$dst" : 'X\(//\)$' \| \ X"$dst" : 'X\(/\)' \| . 2>/dev/null || echo X"$dst" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q' ` test -d "$dstdir" dstdir_status=$? fi fi obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # Create intermediate dirs using mode 755 as modified by the umask. # This is like FreeBSD 'install' as of 1997-10-28. umask=`umask` case $stripcmd.$umask in # Optimize common cases. *[2367][2367]) mkdir_umask=$umask;; .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; *[0-7]) mkdir_umask=`expr $umask + 22 \ - $umask % 100 % 40 + $umask % 20 \ - $umask % 10 % 4 + $umask % 2 `;; *) mkdir_umask=$umask,go-w;; esac # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false case $umask in *[123567][0-7][0-7]) # POSIX mkdir -p sets u+wx bits regardless of umask, which # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 if (umask $mkdir_umask && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writeable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. ls_ld_tmpdir=`ls -ld "$tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/d" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null fi trap '' 0;; esac;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # The umask is ridiculous, or mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; -*) prefix='./';; *) prefix='';; esac eval "$initialize_posix_glob" oIFS=$IFS IFS=/ $posix_glob set -f set fnord $dstdir shift $posix_glob set +f IFS=$oIFS prefixes= for d do test -z "$d" && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask=$mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/_inst.$$_ rmtmp=$dstdir/_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && eval "$initialize_posix_glob" && $posix_glob set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && $posix_glob set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd -f "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: yash-2.49/job.c000066400000000000000000001241271354143602500133440ustar00rootroot00000000000000/* Yash: yet another shell */ /* job.c: job control */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "job.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "exec.h" #include "option.h" #include "plist.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "yash.h" #if YASH_ENABLE_LINEEDIT # include "xfnmatch.h" # include "lineedit/complete.h" # ifndef FG_DONT_SAVE_TERMINAL # include "lineedit/terminfo.h" # endif #endif static inline job_T *get_job(size_t jobnumber) __attribute__((pure)); static inline void free_job(job_T *job); static void trim_joblist(void); static void set_current_jobnumber(size_t jobnumber); static size_t find_next_job(size_t numlimit); static void apply_curstop(void); static int calc_status(int status) __attribute__((const)); static inline int calc_status_of_process(const process_T *p) __attribute__((nonnull,pure)); static wchar_t *get_job_name(const job_T *job) __attribute__((nonnull,warn_unused_result)); static char *get_process_status_string(const process_T *p, bool *needfree) __attribute__((nonnull,malloc,warn_unused_result)); static char *get_job_status_string(const job_T *job, bool *needfree) __attribute__((nonnull,malloc,warn_unused_result)); static int print_job_status(size_t jobnumber, bool changedonly, bool verbose, bool remove_done, FILE *f) __attribute__((nonnull)); static size_t get_jobnumber_from_name(const wchar_t *name) __attribute__((nonnull,pure)); static size_t get_jobnumber_from_pid(long pid) __attribute__((pure)); static bool jobs_builtin_print_job(size_t jobnumber, bool verbose, bool changedonly, bool pgidonly, bool runningonly, bool stoppedonly); static int continue_job(size_t jobnumber, job_T *job, bool fg) __attribute__((nonnull)); static int wait_for_job_by_jobspec(const wchar_t *jobspec) __attribute__((nonnull)); static bool wait_builtin_has_job(bool jobcontrol); /* The list of jobs. * `joblist.contents[ACTIVE_JOBNO]' is a special job that is called "active * job", the job that is currently being executed. * The list length is always non-zero. */ static plist_T joblist; /* number of the current/previous jobs. 0 if none. */ static size_t current_jobnumber, previous_jobnumber; /* Initializes the job list. */ void init_job(void) { assert(joblist.contents == NULL); pl_init(&joblist); pl_add(&joblist, NULL); } /* Sets the active job. */ void set_active_job(job_T *job) { assert(ACTIVE_JOBNO < joblist.length); assert(joblist.contents[ACTIVE_JOBNO] == NULL); joblist.contents[ACTIVE_JOBNO] = job; } /* Moves the active job into the job list. * If the newly added job is stopped, it becomes the current job. * If `current' is true or there is no current job, the newly added job becomes * the current job if there is no stopped job. */ void add_job(bool current) { job_T *job = joblist.contents[ACTIVE_JOBNO]; size_t jobnumber; assert(job != NULL); joblist.contents[ACTIVE_JOBNO] = NULL; /* if there is an empty element in the list, use it */ for (jobnumber = 1; jobnumber < joblist.length; jobnumber++) { if (joblist.contents[jobnumber] == NULL) { joblist.contents[jobnumber] = job; goto set_current; } } /* if there is no empty, append at the end of the list */ pl_add(&joblist, job); set_current: assert(joblist.contents[jobnumber] == job); if (job->j_status == JS_STOPPED || current) set_current_jobnumber(jobnumber); else set_current_jobnumber(current_jobnumber); } /* Returns the job of the specified number or NULL if not found. */ job_T *get_job(size_t jobnumber) { return (jobnumber < joblist.length) ? joblist.contents[jobnumber] : NULL; } /* Removes the job of the specified number. * If the job is the current/previous job, the current/previous job is reset * (another job is assigned to it). */ void remove_job(size_t jobnumber) { free_job(get_job(jobnumber)); joblist.contents[jobnumber] = NULL; trim_joblist(); set_current_jobnumber(current_jobnumber); } /* Removes all jobs unconditionally. */ void remove_all_jobs(void) { for (size_t i = 0; i < joblist.length; i++) { free_job(joblist.contents[i]); joblist.contents[i] = NULL; } trim_joblist(); current_jobnumber = previous_jobnumber = 0; } /* Frees the specified job. */ void free_job(job_T *job) { if (job != NULL) { #ifndef NDEBUG assert(!job->j_beingwaitedfor); #endif for (size_t i = 0; i < job->j_pcount; i++) free(job->j_procs[i].pr_name); free(job); } } /* Shrink the job list, removing unused elements. */ void trim_joblist(void) { if (joblist.maxlength > 20 && joblist.maxlength / 2 > joblist.length) { pl_setmax(&joblist, joblist.length * 2); } else { size_t tail = joblist.length; while (tail > 1 && joblist.contents[tail - 1] == NULL) tail--; assert(tail > 0); pl_remove(&joblist, tail, SIZE_MAX); } } /* Sets the `j_legacy' flags of all jobs. * All the jobs will be no longer job-controlled. */ void neglect_all_jobs(void) { for (size_t i = 0; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (job != NULL) job->j_legacy = true; } current_jobnumber = previous_jobnumber = 0; } /* Current/previous job selection discipline: * * - When there is one or more stopped jobs, the current job must be one of * them. * - When there are more than one stopped job, the previous job must be one of * them but the current one. * - The current job becomes the previous job when another job becomes the * current. * * - When a foreground job is stopped, it becomes the current job. * - When an asynchronous command is executed and the "curasync" option is set, * it becomes the current job. * - When a job is continued by the "bg" command and the "curbg" option is set, * it becomes the current job. * - When a job is stopped and the "curstop" option is set, it becomes the * current job. * * - The "wait" command doesn't change the current and previous jobs. */ /* Sets the current job number to the specified one and resets the previous job * number. If the specified job number is not used, a job is arbitrarily chosen * for the current. If there is one or more stopped jobs and the one specified * by the argument is not stopped, the current job is not changed. */ /* This function must be called whenever a job is added to or removed from the * job list or any job's status has been changed. */ void set_current_jobnumber(size_t jobnumber) { size_t stopcount = stopped_job_count(); const job_T *newcurrent = get_job(jobnumber); if (newcurrent == NULL || (stopcount > 0 && newcurrent->j_status != JS_STOPPED)) { jobnumber = current_jobnumber; newcurrent = get_job(jobnumber); if (newcurrent == NULL || (stopcount > 0 && newcurrent->j_status != JS_STOPPED)) { jobnumber = previous_jobnumber; newcurrent = get_job(jobnumber); if (newcurrent == NULL || (stopcount > 0 && newcurrent->j_status != JS_STOPPED)) jobnumber = find_next_job(ACTIVE_JOBNO); } } if (jobnumber != current_jobnumber) { size_t oldcurrentnum = current_jobnumber; current_jobnumber = jobnumber; jobnumber = oldcurrentnum; } else { jobnumber = previous_jobnumber; } const job_T *newprevious = get_job(jobnumber); if (newprevious == NULL || jobnumber == current_jobnumber || (stopcount > 1 && newprevious->j_status != JS_STOPPED)) { jobnumber = previous_jobnumber; newprevious = get_job(jobnumber); if (newprevious == NULL || jobnumber == current_jobnumber || (stopcount > 1 && newprevious->j_status != JS_STOPPED)) jobnumber = find_next_job(current_jobnumber); } previous_jobnumber = jobnumber; } /* Returns an arbitrary job number except the specified. * The returned number is suitable for the current/previous jobs. * If there is no job to pick out, 0 is returned. * Stopped jobs are preferred to running/finished jobs. * If there are more than one stopped jobs, the previous job is preferred. */ size_t find_next_job(size_t excl) { if (previous_jobnumber != excl) { job_T *job = get_job(previous_jobnumber); if (job != NULL && job->j_status == JS_STOPPED) return previous_jobnumber; } size_t jobnumber = joblist.length; while (--jobnumber > 0) { if (jobnumber != excl) { job_T *job = get_job(jobnumber); if (job != NULL && job->j_status == JS_STOPPED) return jobnumber; } } jobnumber = joblist.length; while (--jobnumber > 0) { if (jobnumber != excl) { job_T *job = get_job(jobnumber); if (job != NULL) return jobnumber; } } return 0; } /* If the "curstop" option is set and there is a job which has been stopped and * whose `j_statuschanged' flag is set, make it the current job. */ void apply_curstop(void) { if (shopt_curstop) { for (size_t i = 0; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (job != NULL) if (job->j_status == JS_STOPPED && job->j_statuschanged) set_current_jobnumber(i); } } set_current_jobnumber(current_jobnumber); } /* Counts the number of jobs in the job list. */ size_t job_count(void) { size_t count = 0; for (size_t i = 0; i < joblist.length; i++) { const job_T *job = joblist.contents[i]; if (job == NULL) continue; if (job->j_status == JS_DONE && !job->j_statuschanged) continue; // Ignore finished jobs that have already been reported. count++; } return count; } /* Counts the number of stopped jobs in the job list. */ size_t stopped_job_count(void) { size_t count = 0; for (size_t i = 0; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (job != NULL && job->j_status == JS_STOPPED) count++; } return count; } /* Updates the info about the jobs in the job list. * This function doesn't block. */ void do_wait(void) { pid_t pid; int status; #if HAVE_WCONTINUED static int waitpidoption = WUNTRACED | WCONTINUED | WNOHANG; #else const int waitpidoption = WUNTRACED | WNOHANG; #endif start: pid = waitpid(-1, &status, waitpidoption); if (pid < 0) { switch (errno) { case EINTR: goto start; /* try again */ case ECHILD: return; /* there are no child processes */ #if HAVE_WCONTINUED /* Even when the WCONTINUED flag is defined in the * header, the OS kernel may not support it. We try again without * the flag if it is rejected. */ case EINVAL: if (waitpidoption & WCONTINUED) { waitpidoption &= ~WCONTINUED; goto start; } #endif ; /* falls thru! */ default: xerror(errno, "waitpid"); return; } } else if (pid == 0) { /* no more jobs to be updated */ return; } size_t jobnumber, pnumber; job_T *job; process_T *pr; /* determine `jobnumber', `job' and `pr' from `pid' */ for (jobnumber = 0; jobnumber < joblist.length; jobnumber++) if ((job = joblist.contents[jobnumber]) != NULL) for (pnumber = 0; pnumber < job->j_pcount; pnumber++) if ((pr = &job->j_procs[pnumber])->pr_pid == pid && pr->pr_status != JS_DONE) goto found; /* If `pid' was not found in the job list, we simply ignore it. This may * happen on some occasions: e.g. the job has been "disown"ed. */ goto start; found: pr->pr_statuscode = status; if (WIFEXITED(status) || WIFSIGNALED(status)) pr->pr_status = JS_DONE; if (WIFSTOPPED(status)) pr->pr_status = JS_STOPPED; #ifdef HAVE_WCONTINUED if (WIFCONTINUED(status)) pr->pr_status = JS_RUNNING; /* On FreeBSD, when WIFCONTINUED is true, WIFSIGNALED is also true. We must * be careful about the order of these checks. */ #endif /* decide the job status from the process status: * - JS_RUNNING if any of the processes is running. * - JS_STOPPED if no processes are running but some are stopped. * - JS_DONE if all the processes are finished. */ jobstatus_T oldstatus = job->j_status; bool anyrunning = false, anystopped = false; /* check if there are running/stopped processes */ for (size_t i = 0; i < job->j_pcount; i++) { switch (job->j_procs[i].pr_status) { case JS_RUNNING: anyrunning = true; goto out_of_loop; case JS_STOPPED: anystopped = true; break; default: break; } } out_of_loop: job->j_status = anyrunning ? JS_RUNNING : anystopped ? JS_STOPPED : JS_DONE; if (job->j_status != oldstatus) job->j_statuschanged = true; goto start; } /* Waits for the specified job to finish (or stop). * `jobnumber' must be a valid job number. * If `return_on_stop' is false, waits for the job to finish. * Otherwise, waits for the job to finish or stop. * If `interruptible' is true, this function can be canceled by SIGINT. * If `return_on_trap' is true, this function returns false immediately after * trap actions are performed. Otherwise, traps are not handled. * This function returns immediately if the job is already finished/stopped or * is not a child of this shell process. * Returns the signal number if interrupted, or zero if successful. */ /* In most cases, you should call `put_foreground' to bring the shell back to * foreground after calling `wait_for_job' if `doing_job_control_now' is true. */ int wait_for_job(size_t jobnumber, bool return_on_stop, bool interruptible, bool return_on_trap) { int signum = 0; job_T *job = joblist.contents[jobnumber]; if (!job->j_legacy) { #ifndef NDEBUG assert(!job->j_beingwaitedfor); job->j_beingwaitedfor = true; #endif for (;;) { if (job->j_status == JS_DONE) break; if (return_on_stop && job->j_status == JS_STOPPED) break; signum = wait_for_sigchld(interruptible, return_on_trap); if (signum != 0) break; } #ifndef NDEBUG job->j_beingwaitedfor = false; #endif } return signum; } /* Waits for the specified child process to finish (or stop). * `cpid' is the process ID of the child process to wait for. This must not be * in the job list. * `cpgid' is the process group ID of the child. If the child's PGID is the same * as that of the parent, `cpgid' must be 0. * If `return_on_stop' is false, waits for the job to finish. * Otherwise, waits for the job to finish or stop. * Traps are not handled in this function. * There must be no active job when this function is called. * If `return_on_stop' is true and the child is stopped, this function returns * a pointer to a pointer to a wide string. The caller must assign a pointer to * a newly malloced wide string to the variable the return value points to. * This string is used as the name of the new stopped job. * If the child exited, this function returns NULL. * The exit status is assigned to `laststatus' in any case. */ wchar_t **wait_for_child(pid_t cpid, pid_t cpgid, bool return_on_stop) { job_T *job = xmalloc(add(sizeof *job, sizeof *job->j_procs)); job->j_pgid = cpgid; job->j_status = JS_RUNNING; job->j_statuschanged = false; job->j_legacy = false; #ifndef NDEBUG job->j_beingwaitedfor = false; #endif job->j_pcount = 1; job->j_procs[0].pr_pid = cpid; job->j_procs[0].pr_status = JS_RUNNING; job->j_procs[0].pr_statuscode = 0; job->j_procs[0].pr_name = NULL; set_active_job(job); wait_for_job(ACTIVE_JOBNO, return_on_stop, false, false); if (doing_job_control_now) put_foreground(shell_pgid); laststatus = calc_status_of_job(job); if (job->j_status == JS_DONE) { notify_signaled_job(ACTIVE_JOBNO); remove_job(ACTIVE_JOBNO); return NULL; } else { add_job(true); return &job->j_procs[0].pr_name; } } /* Returns the process group ID of the specified job. * If no valid job is found, an error message is printed and -1 is returned. * `jobname' may have a preceding '%' sign. */ pid_t get_job_pgid(const wchar_t *jobname) { size_t jobnumber = get_jobnumber_from_name( (jobname[0] == L'%') ? &jobname[1] : jobname); const job_T *job; if (jobnumber >= joblist.length) { xerror(0, Ngt("job specification `%ls' is ambiguous"), jobname); return -1; } else if (jobnumber == 0 || (job = joblist.contents[jobnumber]) == NULL || job->j_legacy) { xerror(0, Ngt("no such job `%ls'"), jobname); return -1; } else if (job->j_pgid == 0) { xerror(0, Ngt("`%ls' is not a job-controlled job"), jobname); return -1; } else { return job->j_pgid; } } /* Puts the specified process group in the foreground. * `pgrp' must be a valid process group ID and `doing_job_control_now' must be * true. */ void put_foreground(pid_t pgrp) { sigset_t blockss, savess; assert(doing_job_control_now); assert(pgrp > 0); sigemptyset(&blockss); sigaddset(&blockss, SIGTTOU); sigemptyset(&savess); sigprocmask(SIG_BLOCK, &blockss, &savess); tcsetpgrp(ttyfd, pgrp); sigprocmask(SIG_SETMASK, &savess, NULL); } /* Ensures the current shell process is in the foreground. * The shell process is stopped by SIGTTOU until it is put in the foreground. * This function requires `doing_job_control_now' to be true. */ /* This function prevents the job-control shell from mangling the terminal while * another shell is using it. */ void ensure_foreground(void) { /* This function calls `tcsetpgrp' with the default SIGTTOU handler. If the * shell is in the background, it will receive SIGTTOU and get stopped until * it is continued in the foreground. */ struct sigaction dflsa, savesa; sigset_t blockss, savess; assert(doing_job_control_now); assert(shell_pgid > 0); dflsa.sa_handler = SIG_DFL; dflsa.sa_flags = 0; sigemptyset(&dflsa.sa_mask); sigemptyset(&savesa.sa_mask); sigaction(SIGTTOU, &dflsa, &savesa); sigemptyset(&blockss); sigaddset(&blockss, SIGTTOU); sigemptyset(&savess); sigprocmask(SIG_UNBLOCK, &blockss, &savess); tcsetpgrp(ttyfd, shell_pgid); sigprocmask(SIG_SETMASK, &savess, NULL); sigaction(SIGTTOU, &savesa, NULL); } /* Computes the exit status from the status code returned by `waitpid'. */ int calc_status(int status) { if (WIFEXITED(status)) return WEXITSTATUS(status); #ifdef WIFCONTINUED if (WIFCONTINUED(status)) return Exit_SUCCESS; /* On FreeBSD, when WIFCONTINUED is true, WIFSIGNALED is also true. We must * be careful about the order of these checks. */ #endif if (WIFSIGNALED(status)) return WTERMSIG(status) + TERMSIGOFFSET; if (WIFSTOPPED(status)) return WSTOPSIG(status) + TERMSIGOFFSET; assert(false); } /* Computes the exit status of the specified process. * The process state must be JS_DONE or JS_STOPPED. */ int calc_status_of_process(const process_T *p) { int s = p->pr_statuscode; return (p->pr_pid == 0) ? s : calc_status(s); } /* Computes the exit status of the specified job. * The job state must be JS_DONE or JS_STOPPED. */ int calc_status_of_job(const job_T *job) { switch (job->j_status) { case JS_DONE: if (!shopt_pipefail) return calc_status_of_process(&job->j_procs[job->j_pcount - 1]); for (size_t i = job->j_pcount; i-- > 0; ) { int status = calc_status_of_process(&job->j_procs[i]); if (status != Exit_SUCCESS) return status; } return Exit_SUCCESS; case JS_STOPPED: for (size_t i = job->j_pcount; i-- > 0; ) { if (job->j_procs[i].pr_status == JS_STOPPED) return calc_status(job->j_procs[i].pr_statuscode); } /* falls thru! */ default: assert(false); } } /* Returns the name of the specified job. * If the job has only one process, `job->j_procs[0].pr_name' is returned. * Otherwise, the names of all the process are concatenated and returned, which * must be freed by the caller. */ wchar_t *get_job_name(const job_T *job) { if (job->j_pcount == 1) return job->j_procs[0].pr_name; xwcsbuf_T buf; wb_init(&buf); for (size_t i = 0; i < job->j_pcount; i++) { if (i > 0) wb_cat(&buf, L" | "); wb_cat(&buf, job->j_procs[i].pr_name); } return wb_towcs(&buf); } /* Returns a string that describes the status of the specified process * such as "Running" and "Stopped(SIGTSTP)". * The returned string must be freed by the caller iff `*needfree' is assigned * true, otherwise it must not be modified or freed. */ char *get_process_status_string(const process_T *p, bool *needfree) { int status, sig; switch (p->pr_status) { case JS_RUNNING: *needfree = false; return (char *) gt("Running"); case JS_STOPPED: *needfree = true; return malloc_printf(gt("Stopped(SIG%ls)"), get_signal_name(WSTOPSIG(p->pr_statuscode))); case JS_DONE: status = p->pr_statuscode; if (p->pr_pid == 0) goto exitstatus; if (WIFEXITED(status)) { status = WEXITSTATUS(status); exitstatus: if (status == Exit_SUCCESS) { *needfree = false; return (char *) gt("Done"); } else { *needfree = true; return malloc_printf(gt("Done(%d)"), status); } } else { assert(WIFSIGNALED(status)); *needfree = true; sig = WTERMSIG(status); #ifdef WCOREDUMP if (WCOREDUMP(sig)) return malloc_printf(gt("Killed (SIG%ls: core dumped)"), get_signal_name(sig)); #endif return malloc_printf(gt("Killed (SIG%ls)"), get_signal_name(sig)); } } assert(false); } /* Returns a string that describes the status of the specified job * such as "Running" and "Stopped(SIGTSTP)". * The returned string must be freed by the caller iff `*needfree' is assigned * true, otherwise it must not be modified or freed. */ char *get_job_status_string(const job_T *job, bool *needfree) { switch (job->j_status) { case JS_RUNNING: *needfree = false; return (char *) gt("Running"); case JS_STOPPED: /* find a stopped process */ for (size_t i = job->j_pcount; ; ) if (job->j_procs[--i].pr_status == JS_STOPPED) return get_process_status_string(&job->j_procs[i], needfree); assert(false); case JS_DONE: return get_process_status_string( &job->j_procs[job->j_pcount - 1], needfree); } assert(false); } /* Returns true iff there is any job whose status has been changed but not yet * reported. If this function returns false, `print_job_status_all' is nop. */ bool any_job_status_has_changed(void) { for (size_t i = 1; i < joblist.length; i++) { const job_T *job = get_job(i); if (job != NULL && job->j_statuschanged) return true; } return false; } /* Prints the status of the specified job. * If `remove_done' is true, finished jobs are removed from the job list after * the status is printed. * If the specified job doesn't exist, nothing is printed (it isn't an error). * If `changedonly' is true, the job is printed only if the `j_statuschanged' * flag is true. * If `verbose' is true, the status is printed in the process-wise format rather * than the usual job-wise format. * Returns zero if successful. Returns errno if `fprintf' failed. */ int print_job_status(size_t jobnumber, bool changedonly, bool verbose, bool remove_done, FILE *f) { job_T *job = get_job(jobnumber); if (job == NULL) return 0; if (changedonly && !job->j_statuschanged) return 0; char current; if (jobnumber == current_jobnumber) current = '+'; else if (jobnumber == previous_jobnumber) current = '-'; else current = ' '; int result; if (!verbose) { bool needfree; char *status = get_job_status_string(job, &needfree); wchar_t *jobname = get_job_name(job); /* TRANSLATORS: the translated format string can be different * from the original only in the number of spaces. This is required * for POSIX compliance. */ result = fprintf(f, gt("[%zu] %c %-20s %ls\n"), jobnumber, current, status, jobname); result = (result >= 0) ? 0 : errno; if (needfree) free(status); if (jobname != job->j_procs[0].pr_name) free(jobname); } else { bool needfree; pid_t pid = job->j_procs[0].pr_pid; char *status = get_process_status_string( &job->j_procs[posixly_correct ? job->j_pcount - 1 : 0], &needfree); wchar_t *jobname = job->j_procs[0].pr_name; /* TRANSLATORS: the translated format string can be different * from the original only in the number of spaces. This is required * for POSIX compliance. */ result = fprintf(f, gt("[%zu] %c %5jd %-20s %ls\n"), jobnumber, current, (intmax_t) pid, status, jobname); result = (result >= 0) ? 0 : errno; if (needfree) free(status); for (size_t i = 1; result == 0 && i < job->j_pcount; i++) { pid = job->j_procs[i].pr_pid; status = get_process_status_string(&job->j_procs[i], &needfree); jobname = job->j_procs[i].pr_name; /* TRANSLATORS: the translated format string can be different * from the original only in the number of spaces. This is required * for POSIX compliance. */ result = fprintf(f, gt(" %5jd %-20s | %ls\n"), (intmax_t) pid, (posixly_correct ? "" : status), jobname); result = (result >= 0) ? 0 : errno; if (needfree) free(status); } } job->j_statuschanged = false; if (remove_done && job->j_status == JS_DONE) remove_job(jobnumber); return result; } /* Prints the status of jobs which have been changed but not reported. */ void print_job_status_all(void) { apply_curstop(); for (size_t i = 1; i < joblist.length; i++) print_job_status(i, true, false, false, stderr); } /* If the shell is interactive and the specified job has been killed by a * signal other than SIGPIPE, prints a notification to the standard error. * If the signal is SIGINT, only a single newline is printed to the standard * error and the shell is flagged as interrupted. */ void notify_signaled_job(size_t jobnumber) { if (!is_interactive_now) return; job_T *job = get_job(jobnumber); if (job == NULL || job->j_status != JS_DONE) return; process_T *p = &job->j_procs[job->j_pcount - 1]; assert(p->pr_status == JS_DONE); if (p->pr_pid == 0 || !WIFSIGNALED(p->pr_statuscode)) return; int sig = WTERMSIG(p->pr_statuscode); switch (sig) { case SIGINT: fputc('\n', stderr); set_interrupted(); break; case SIGPIPE: break; default: #if HAVE_STRSIGNAL fprintf(stderr, gt("The process was killed by SIG%ls: %s\n"), get_signal_name(sig), strsignal(sig)); #else fprintf(stderr, gt("The process was killed by SIG%ls\n"), get_signal_name(sig)); #endif break; } } /* Returns the job number from the specified job ID string. * If no applicable job is found, zero is returned. * If more than one jobs are found, `joblist.length' is returned. * When `name' is a number, the number is returned if it is a valid job number. * The string must not have the preceding '%'. */ /* "", "%", "+" -> the current job * "-" -> the previous job * "n" (integer) -> the job #n * "xxx" -> the job whose name starts with "xxx" * "?xxx" -> the job whose name contains "xxx" */ size_t get_jobnumber_from_name(const wchar_t *name) { if (name[0] == L'\0' || wcscmp(name, L"%") == 0 || wcscmp(name, L"+") == 0) return current_jobnumber; if (wcscmp(name, L"-") == 0) return previous_jobnumber; if (iswdigit(name[0])) { unsigned long num; if (xwcstoul(name, 10, &num)) return (num <= SIZE_MAX && get_job(num) != NULL) ? num : 0; } bool contain; size_t n = 0; if (name[0] == L'?') { contain = true; name++; } else { contain = false; } for (size_t i = 1; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (job != NULL) { wchar_t *jobname = get_job_name(job); bool match = contain ? wcsstr(jobname, name) != NULL : matchwcsprefix(jobname, name) != NULL; if (jobname != job->j_procs[0].pr_name) free(jobname); if (match) { if (n != 0) return joblist.length; /* more than one found */ else n = i; } } } return n; } /* Returns the number of job that contains a process whose process ID is `pid'. * If not found, 0 is returned. */ size_t get_jobnumber_from_pid(long pid) { size_t jobnumber; if (pid == 0) return 0; for (jobnumber = joblist.length; --jobnumber > 0; ) { job_T *job = joblist.contents[jobnumber]; if (job != NULL) { for (size_t i = 0; i < job->j_pcount; i++) if (job->j_procs[i].pr_pid == pid) goto found; } } found: return jobnumber; } #if YASH_ENABLE_LINEEDIT /* Generates completion candidates for job names matching the pattern. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_job_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_JOB)) return; le_compdebug("adding job candidates"); if (!le_compile_cpatterns(compopt)) return; for (size_t i = 1; i < joblist.length; i++) { const job_T *job = joblist.contents[i]; if (job == NULL) continue; switch (job->j_status) { case JS_RUNNING: if (!(compopt->type & CGT_RUNNING)) continue; break; case JS_STOPPED: if (!(compopt->type & CGT_STOPPED)) continue; break; case JS_DONE: if (!(compopt->type & CGT_DONE)) continue; break; } wchar_t *jobname = get_job_name(job); if (le_wmatch_comppatterns(compopt, jobname)) le_new_candidate(CT_JOB, xwcsdup(jobname), malloc_wprintf(L"%%%zu", i), compopt); if (jobname != job->j_procs[0].pr_name) free(jobname); } } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-ins **********/ /* Options for the "jobs" built-in. */ const struct xgetopt_T jobs_options[] = { { L'l', L"verbose", OPTARG_NONE, true, NULL, }, { L'n', L"new", OPTARG_NONE, false, NULL, }, { L'p', L"pgid-only", OPTARG_NONE, true, NULL, }, { L'r', L"running-only", OPTARG_NONE, false, NULL, }, { L's', L"stopped-only", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "jobs" built-in, which accepts the following options: * -l: be verbose * -n: print the jobs only whose status have changed * -p: print the process ID only * -r: print running jobs only * -s: print stopped jobs only * In the POSIXly correct mode, only -l and -p are available. */ int jobs_builtin(int argc, void **argv) { bool verbose = false, changedonly = false, pgidonly = false; bool runningonly = false, stoppedonly = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, jobs_options, 0)) != NULL) { switch (opt->shortopt) { case L'l': verbose = true; break; case L'n': changedonly = true; break; case L'p': pgidonly = true; break; case L'r': runningonly = true; break; case L's': stoppedonly = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } nextforceexit = true; apply_curstop(); if (xoptind < argc) { /* print the specified jobs */ do { const wchar_t *jobspec = ARGV(xoptind); if (jobspec[0] == L'%') { jobspec++; } else if (posixly_correct) { xerror(0, Ngt("`%ls' is not a valid job specification"), ARGV(xoptind)); continue; } size_t jobnumber = get_jobnumber_from_name(jobspec); if (jobnumber >= joblist.length) { xerror(0, Ngt("job specification `%ls' is ambiguous"), ARGV(xoptind)); } else if (jobnumber == 0 || joblist.contents[jobnumber] == NULL) { xerror(0, Ngt("no such job `%ls'"), ARGV(xoptind)); } else { if (!jobs_builtin_print_job(jobnumber, verbose, changedonly, pgidonly, runningonly, stoppedonly)) return Exit_FAILURE; } } while (++xoptind < argc); } else { /* print all jobs */ for (size_t i = 1; i < joblist.length; i++) { if (!jobs_builtin_print_job(i, verbose, changedonly, pgidonly, runningonly, stoppedonly)) return Exit_FAILURE; } } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Prints the job status. * On an I/O error, an error message is printed to the standard error and false * is returned. */ bool jobs_builtin_print_job(size_t jobnumber, bool verbose, bool changedonly, bool pgidonly, bool runningonly, bool stoppedonly) { job_T *job = get_job(jobnumber); if (job == NULL) return true; if (runningonly && job->j_status != JS_RUNNING) return true; if (stoppedonly && job->j_status != JS_STOPPED) return true; int err; if (pgidonly) { if (changedonly && !job->j_statuschanged) return true; int result = printf("%jd\n", (intmax_t) job->j_pgid); err = (result >= 0) ? 0 : errno; } else { err = print_job_status(jobnumber, changedonly, verbose, true, stdout); } if (err != 0) { xerror(err, Ngt("cannot print to the standard output")); return false; } else { return true; } } #if YASH_ENABLE_HELP const char jobs_help[] = Ngt( "print info about jobs" ); const char jobs_syntax[] = Ngt( "\tjobs [-lnprs] [job...]\n" ); #endif /* The "fg"/"bg" built-in */ int fg_builtin(int argc, void **argv) { bool fg = (wcscmp(argv[0], L"fg") == 0); const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, 0)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (fg && posixly_correct && !validate_operand_count(argc - xoptind, 0, 1)) return Exit_ERROR; if (!doing_job_control_now) { xerror(0, Ngt("job control is disabled")); return Exit_FAILURE; } int status = Exit_SUCCESS; job_T *job; if (xoptind < argc) { do { const wchar_t *jobspec = ARGV(xoptind); if (jobspec[0] == L'%') { jobspec++; } else if (posixly_correct) { xerror(0, Ngt("`%ls' is not a valid job specification"), ARGV(xoptind)); continue; } size_t jobnumber = get_jobnumber_from_name(jobspec); if (jobnumber >= joblist.length) { xerror(0, Ngt("job specification `%ls' is ambiguous"), ARGV(xoptind)); } else if (jobnumber == 0 || (job = joblist.contents[jobnumber]) == NULL || job->j_legacy) { xerror(0, Ngt("no such job `%ls'"), ARGV(xoptind)); } else if (job->j_pgid == 0) { xerror(0, Ngt("`%ls' is not a job-controlled job"), ARGV(xoptind)); } else { status = continue_job(jobnumber, job, fg); } } while (++xoptind < argc); } else { if (current_jobnumber == 0 || (job = joblist.contents[current_jobnumber])->j_legacy) { xerror(0, Ngt("there is no current job")); } else if (job->j_pgid == 0) { xerror(0, Ngt("the current job is not a job-controlled job")); } else { status = continue_job(current_jobnumber, job, fg); } } if (status != 0) return status; if (yash_error_message_count != 0) return Exit_FAILURE; return Exit_SUCCESS; } /* Continues execution of the specified job. * Returns the exit code of the continued job or 0 if it is still running. */ int continue_job(size_t jobnumber, job_T *job, bool fg) { assert(job->j_pgid > 0); assert(!job->j_legacy); wchar_t *name = get_job_name(job); if (fg && posixly_correct) xprintf("%ls\n", name); else xprintf("[%zu] %ls\n", jobnumber, name); if (name != job->j_procs[0].pr_name) free(name); #if YASH_ENABLE_LINEEDIT && !defined(FG_DONT_SAVE_TERMINAL) bool termsave = fg && le_save_terminal(); /* see below */ #endif if (job->j_status != JS_DONE) { if (fg) put_foreground(job->j_pgid); if (kill(-job->j_pgid, SIGCONT) >= 0) job->j_status = JS_RUNNING; } else { if (!fg) xerror(0, Ngt("job %%%zu has already terminated"), jobnumber); } int status; if (fg) { wait_for_job(jobnumber, true, false, false); put_foreground(shell_pgid); #if YASH_ENABLE_LINEEDIT && !defined(FG_DONT_SAVE_TERMINAL) if (termsave) le_restore_terminal(); #endif switch (job->j_status) { case JS_STOPPED: status = calc_status_of_job(job); set_current_jobnumber(jobnumber); break; case JS_DONE: status = calc_status_of_job(job); notify_signaled_job(jobnumber); remove_job(jobnumber); break; default: assert(false); } } else { set_current_jobnumber(shopt_curbg ? jobnumber : current_jobnumber); status = (job->j_status == JS_RUNNING) ? Exit_SUCCESS : Exit_FAILURE; } return status; /* We save the terminal state before continuing a job in the foreground and * restore the state after the job has finished. This is because some * programs leave the terminal in the wrong state if they were at first * invoked in the background. * Programs that change the terminal state generally save the state before * changing it and restore it when they are finished. But, if they are * invoked in the background, they may save the state that is being used by * another program (typically the shell's line-editing), so the state that * they restore is not the normal state. * We try to work around this problem by saving and restoring the terminal * state for the continued programs. */ } #if YASH_ENABLE_HELP const char fg_help[] = Ngt( "run jobs in the foreground" ); const char fg_syntax[] = Ngt( "\tfg [job...]\n" ); const char bg_help[] = Ngt( "run jobs in the background" ); const char bg_syntax[] = Ngt( "\tbg [job...]\n" ); #endif /* YASH_ENABLE_HELP */ /* The "wait" built-in */ int wait_builtin(int argc, void **argv) { bool jobcontrol = doing_job_control_now; int status = Exit_SUCCESS; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, 0)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (xoptind < argc) { /* wait for the specified jobs */ for (; xoptind < argc; xoptind++) { int jobstatus = wait_for_job_by_jobspec(ARGV(xoptind)); if (jobstatus < 0) { status = -jobstatus; break; } status = jobstatus; } } else { /* wait for all jobs */ while (wait_builtin_has_job(jobcontrol)) { status = wait_for_sigchld(jobcontrol, true); if (status) { assert(TERMSIGOFFSET >= 128); status += TERMSIGOFFSET; break; } } } if (yash_error_message_count != 0) return Exit_FAILURE; return status; } /* Finds a job specified by the argument and waits for it. * Returns a negated exit status if interrupted. */ int wait_for_job_by_jobspec(const wchar_t *jobspec) { size_t jobnumber; if (jobspec[0] == L'%') { jobnumber = get_jobnumber_from_name(&jobspec[1]); } else { long pid; if (!xwcstol(jobspec, 10, &pid) || pid < 0) { xerror(0, Ngt("`%ls' is not a valid job specification"), jobspec); return Exit_FAILURE; } jobnumber = get_jobnumber_from_pid(pid); } if (jobnumber >= joblist.length) { xerror(0, Ngt("job specification `%ls' is ambiguous"), jobspec); return Exit_FAILURE; } job_T *job; if (jobnumber == 0 || (job = joblist.contents[jobnumber]) == NULL || job->j_legacy) return Exit_NOTFOUND; int signal = wait_for_job(jobnumber, doing_job_control_now, doing_job_control_now, true); if (signal != 0) { assert(TERMSIGOFFSET >= 128); return -(signal + TERMSIGOFFSET); } int status = calc_status_of_job(job); if (job->j_status != JS_RUNNING) { if (doing_job_control_now && is_interactive_now && !posixly_correct) print_job_status(jobnumber, false, false, true, stdout); else if (job->j_status == JS_DONE) remove_job(jobnumber); } return status; } /* Checks if the shell has any job to wait for. */ bool wait_builtin_has_job(bool jobcontrol) { /* print/remove already-finished jobs */ for (size_t i = 1; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (jobcontrol && is_interactive_now && !posixly_correct) print_job_status(i, true, false, false, stdout); if (job != NULL && (job->j_legacy || job->j_status == JS_DONE)) remove_job(i); } /* see if we have jobs to wait for. */ for (size_t i = 1; i < joblist.length; i++) { job_T *job = joblist.contents[i]; if (job != NULL && (!jobcontrol || job->j_status == JS_RUNNING)) return true; } return false; } #if YASH_ENABLE_HELP const char wait_help[] = Ngt( "wait for jobs to terminate" ); const char wait_syntax[] = Ngt( "\twait [job or process_id...]\n" ); #endif /* The "disown" built-in, which accepts the following option: * -a: disown all jobs */ int disown_builtin(int argc, void **argv) { bool all = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, all_help_options, 0)) != NULL) { switch (opt->shortopt) { case L'a': all = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (all) { remove_all_jobs(); } else if (xoptind < argc) { do { const wchar_t *jobspec = ARGV(xoptind); if (jobspec[0] == L'%') { jobspec++; } else if (posixly_correct) { xerror(0, Ngt("`%ls' is not a valid job specification"), ARGV(xoptind)); continue; } size_t jobnumber = get_jobnumber_from_name(jobspec); if (jobnumber >= joblist.length) { xerror(0, Ngt("job specification `%ls' is ambiguous"), ARGV(xoptind)); } else if (jobnumber == 0 || joblist.contents[jobnumber] == NULL) { xerror(0, Ngt("no such job `%ls'"), ARGV(xoptind)); } else { remove_job(jobnumber); } } while (++xoptind < argc); } else { if (current_jobnumber == 0 || get_job(current_jobnumber) == NULL) xerror(0, Ngt("there is no current job")); else remove_job(current_jobnumber); } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if YASH_ENABLE_HELP const char disown_help[] = Ngt( "disown jobs" ); const char disown_syntax[] = Ngt( "\tdisown [job...]\n" "\tdisown -a\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/job.h000066400000000000000000000103771354143602500133520ustar00rootroot00000000000000/* Yash: yet another shell */ /* job.h: job control */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_JOB_H #define YASH_JOB_H #include #include #include "xgetopt.h" /* status of job/process */ typedef enum jobstatus_T { JS_RUNNING, JS_STOPPED, JS_DONE, } jobstatus_T; /* info about a process in a job */ typedef struct process_T { pid_t pr_pid; /* process ID */ jobstatus_T pr_status; int pr_statuscode; wchar_t *pr_name; /* process name made from command line */ } process_T; /* If `pr_pid' is 0, the process was finished without `fork'ing from the shell. * In this case, `pr_status' is JS_DONE and `pr_statuscode' is the exit status. * If `pr_pid' is a positive number, it's the process ID. In this case, * `pr_statuscode' is the status code returned by `waitpid'. */ /* info about a job */ typedef struct job_T { pid_t j_pgid; /* process group ID */ jobstatus_T j_status; _Bool j_statuschanged; /* job's status not yet reported? */ _Bool j_legacy; /* not a true child of the shell? */ #ifndef NDEBUG _Bool j_beingwaitedfor; /* wait_for_job is awaiting? */ #endif size_t j_pcount; /* # of processes in `j_procs' */ process_T j_procs[]; /* info about processes */ } job_T; /* When job control is off, `j_pgid' is 0 since the job shares the process group * ID with the shell. * In subshells, the `j_legacy' flag is set to indicate that the job is not * a direct child of the current shell process. */ /* job number of the active job */ #define ACTIVE_JOBNO 0 /* When a process is stopped/terminated by a signal, this value is added to the * signal number to make the value of the exit status. * 128 in bash/zsh/dash/pdksh/mksh/posh, 256 in ksh. */ #ifndef TERMSIGOFFSET #define TERMSIGOFFSET 384 #endif extern void init_job(void); extern void set_active_job(job_T *job) __attribute__((nonnull)); extern void add_job(_Bool current); extern void remove_job(size_t jobnumber); extern void remove_job_nofitying_signal(size_t jobnumber); extern void remove_all_jobs(void); extern void neglect_all_jobs(void); extern size_t job_count(void) __attribute__((pure)); extern size_t stopped_job_count(void) __attribute__((pure)); extern void do_wait(void); extern int wait_for_job(size_t jobnumber, _Bool return_on_stop, _Bool interruptible, _Bool return_on_trap); extern wchar_t **wait_for_child(pid_t cpid, pid_t cpgid, _Bool return_on_stop); extern pid_t get_job_pgid(const wchar_t *jobname) __attribute__((pure)); extern void put_foreground(pid_t pgrp); extern void ensure_foreground(void); extern int calc_status_of_job(const job_T *job) __attribute__((pure,nonnull)); extern _Bool any_job_status_has_changed(void) __attribute__((pure)); extern void print_job_status_all(void); extern void notify_signaled_job(size_t jobnumber); extern int jobs_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char jobs_help[], jobs_syntax[]; #endif extern const struct xgetopt_T jobs_options[]; extern int fg_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char fg_help[], fg_syntax[], bg_help[], bg_syntax[]; #endif extern int wait_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char wait_help[], wait_syntax[]; #endif extern int disown_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char disown_help[], disown_syntax[]; #endif #endif /* YASH_JOB_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/000077500000000000000000000000001354143602500142145ustar00rootroot00000000000000yash-2.49/lineedit/Makefile.in000066400000000000000000000056441354143602500162720ustar00rootroot00000000000000# Makefile.in for yash: yet another shell # (C) 2007-2012 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . .POSIX: .SUFFIXES: .c .h .d .o .a @MAKE_SHELL@ topdir = .. subdir = lineedit CC = @CC@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ LDFLAGS = @LDFLAGS@ LDLIBS = @LDLIBS@ AR = @AR@ ARFLAGS = @ARFLAGS@ SOURCES = complete.c compparse.c display.c editing.c keymap.c lineedit.c terminfo.c trie.c HEADERS = complete.h compparse.h display.h editing.h key.h keymap.h lineedit.h terminfo.h trie.h OBJS = complete.o compparse.o display.o editing.o keymap.o lineedit.o terminfo.o trie.o TARGET = lineedit.a YASH = @TARGET@ BYPRODUCTS = commands.in *.dSYM all: $(TARGET) .c.o: @rm -f $@ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< $(TARGET): $(OBJS) $(AR) $(ARFLAGS) $@ $(OBJS) keymap.o: commands.in commands.in: editing.h -@echo creating $@... @grep 'cmd_.*/\*C\*/$$' $? | \ LC_ALL=C sed -e 's/\(cmd_[0-9a-zA-Z_]*\).*/\1/' -e 's/.*cmd_//' \ -e 'h' -e 's/_/-/g' -e 'G' -e 's/\n/ /' | \ LC_ALL=C sort -k 1 | \ sed -e 's/ /", cmd_/' -e 's/^/{ "/' -e 's/$$/, },/' > $@ DISTFILES = $(SOURCES) $(SOURCES:.c=.d) $(HEADERS) Makefile.in distfiles: makedeps $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(topdir)/$(DISTTARGETDIR) makedeps: _PHONY @(cd $(topdir) && $(MAKE) $(YASH)) $(topdir)/$(YASH) $(topdir)/makedeps.yash $(SOURCES) # ctags conforms to POSIX, but etags and cscope do not. CTAGS = @CTAGS@ CTAGSARGS = @CTAGSARGS@ ETAGS = @ETAGS@ ETAGSARGS = @ETAGSARGS@ CSCOPE = @CSCOPE@ CSCOPEARGS = @CSCOPEARGS@ tags: $(SOURCES) $(HEADERS) $(CTAGS) $(CTAGSARGS) TAGS: $(SOURCES) $(HEADERS) $(ETAGS) $(ETAGSARGS) cscope: cscope.out cscope.out: $(SOURCES) $(HEADERS) $(CSCOPE) $(CSCOPEARGS) mostlyclean: -rm -rf $(OBJS) $(BYPRODUCTS) clean: mostlyclean -rm -rf $(TARGET) distclean: clean -rm -rf Makefile tags TAGS cscope.out maintainer-clean: distclean -rm -rf $(SOURCES:.c=.d) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) .PHONY: all distfiles copy-distfiles makedeps cscope mostlyclean clean distclean maintainer-clean _PHONY: @MAKE_INCLUDE@ complete.d @MAKE_INCLUDE@ compparse.d @MAKE_INCLUDE@ display.d @MAKE_INCLUDE@ editing.d @MAKE_INCLUDE@ keymap.d @MAKE_INCLUDE@ lineedit.d @MAKE_INCLUDE@ terminfo.d @MAKE_INCLUDE@ trie.d yash-2.49/lineedit/complete.c000066400000000000000000001361601354143602500161770ustar00rootroot00000000000000/* Yash: yet another shell */ /* complete.c: command line completion */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "complete.h" #include #include #include #include #if HAVE_GETGRENT # include #endif #if HAVE_GETTEXT # include #endif #if HAVE_GETHOSTENT # include #endif #if HAVE_GETPWENT # include #endif #include #include #include #include #include #include #include #include #include #include "../builtin.h" #include "../exec.h" #include "../expand.h" #include "../hashtable.h" #include "../option.h" #include "../parser.h" #include "../path.h" #include "../plist.h" #include "../redir.h" #include "../sig.h" #include "../strbuf.h" #include "../util.h" #include "../variable.h" #include "../xfnmatch.h" #include "../yash.h" #include "compparse.h" #include "display.h" #include "editing.h" #include "lineedit.h" #include "terminfo.h" #if HAVE_WCSCASECMP # ifndef wcscasecmp extern int wcscasecmp(const wchar_t *s1, const wchar_t *s2) __attribute__((nonnull)); # endif #endif #if HAVE_GETPWENT # ifndef setpwent extern void setpwent(void); # endif # ifndef getpwent extern struct passwd *getpwent(void); # endif # ifndef endpwent extern void endpwent(void); # endif #endif #if HAVE_GETGRENT # if 0 /* avoid conflict on BSD */ extern void setgrent(void); # endif # ifndef getgrent extern struct group *getgrent(void); # endif # ifndef endgrent extern void endgrent(void); # endif #endif #if HAVE_GETHOSTENT # if 0 /* avoid conflict on SunOS */ extern void sethostent(int); # endif # ifndef gethostent extern struct hostent *gethostent(void); # endif # if 0 /* avoid conflict on SunOS */ extern void endhostent(void); # endif #endif static void select_candidate(void selector(int offset), int offset) __attribute__((nonnull)); static void select_candidate_by_offset(int offset); static void free_candidate(void *c) __attribute__((nonnull)); static void free_context(le_context_T *ctxt); static void sort_candidates(void); static int sort_candidates_cmp(const void *cp1, const void *cp2) __attribute__((nonnull)); static void print_context_info(const le_context_T *ctxt) __attribute__((nonnull)); static void print_compopt_info(const le_compopt_T *compopt) __attribute__((nonnull)); static void execute_completion_function(void); static void complete_command_default(void); static void simple_completion(le_candgentype_T type); static void generate_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static bool le_match_patterns(const le_comppattern_T *ps, const char *s) __attribute__((nonnull(2))); static bool le_wmatch_patterns(const le_comppattern_T *ps, const wchar_t *s) __attribute__((nonnull(2))); static void generate_file_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_external_command_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_keyword_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_logname_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_group_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_host_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); static void generate_candidates_from_words( le_candtype_T type, void *const *words, const wchar_t *description, const le_compopt_T *compopt) __attribute__((nonnull(2,4))); static void word_completion(size_t count, ...) __attribute__((nonnull)); static size_t get_common_prefix_length(void) __attribute__((pure)); static void update_main_buffer(bool subst, bool finish); static void insert_to_main_buffer(wchar_t c); static bool need_subst(void); static void substitute_source_word_all(void); static void quote(xwcsbuf_T *restrict buf, const wchar_t *restrict s, le_quote_T quotetype) __attribute__((nonnull)); /* The current completion context. */ static le_context_T *ctxt = NULL; /* A list that contains the current completion candidates. * The elements pointed to by `le_candidates.contains[*]' are of type * `le_candidate_T'. */ plist_T le_candidates = { .contents = NULL }; /* The index of the currently selected candidate in `le_candidates'. * When no candidate is selected, the index is `le_candidates.length'. */ size_t le_selected_candidate_index; /* The length of the longest common prefix of the current candidates. * The value is ((size_t) -1) when not computed. */ static size_t common_prefix_length; /* Performs command line completion. * Existing candidates are deleted, if any, and candidates are computed from * the current command line. * `lecr' is called after candidate generation. */ void le_complete(le_compresult_T lecr) { if (shopt_le_compdebug) { /* If the `le-compdebug' option is set, the command line is temporarily * cleared during completion. * Note that `shopt_le_compdebug' is referenced only here. During the * completion, we check the value of `le_state' to test if the option * is set. The value of `shopt_le_compdebug' might be changed by a * candidate generator code. */ le_display_finalize(); le_restore_terminal(); le_state = LE_STATE_SUSPENDED | LE_STATE_COMPLETING; le_compdebug("completion start"); } else { le_state |= LE_STATE_COMPLETING; le_allow_terminal_signal(true); } le_complete_cleanup(); pl_init(&le_candidates); common_prefix_length = (size_t) -1; ctxt = le_get_context(); if (le_state_is_compdebug) print_context_info(ctxt); execute_completion_function(); sort_candidates(); le_compdebug("total of %zu candidate(s)", le_candidates.length); /* display the results */ lecr(); if (le_state_is_compdebug) { le_compdebug("completion end"); le_setupterm(true); le_set_terminal(); } else { assert((le_state & (LE_STATE_ACTIVE | LE_STATE_COMPLETING)) == (LE_STATE_ACTIVE | LE_STATE_COMPLETING)); le_allow_terminal_signal(false); /* the terminal size may have been changed during completion, so we * re-check the terminal state here. */ le_display_clear(false); le_setupterm(true); } le_state = LE_STATE_ACTIVE; } /* An `le_compresult_T' function that does nothing. */ void lecr_nop(void) { } /* An `le_compresult_T' function for `cmd_complete'. */ void lecr_normal(void) { if (le_candidates.length == 0) { le_selected_candidate_index = 0; } else if (ctxt->substsrc || need_subst()) { le_selected_candidate_index = 0; substitute_source_word_all(); le_complete_cleanup(); } else if (le_candidates.length == 1) { le_selected_candidate_index = 0; update_main_buffer(false, true); le_complete_cleanup(); } else { le_selected_candidate_index = le_candidates.length; le_display_make_rawvalues(); update_main_buffer(false, false); } } /* An `le_compresult_T' function for `cmd_vi_complete_all'. */ void lecr_substitute_all_candidates(void) { le_selected_candidate_index = 0; if (le_candidates.length == 0) { lebuf_print_alert(true); } else { substitute_source_word_all(); } le_complete_cleanup(); } /* An `le_compresult_T' function for `cmd_vi_complete_max'. */ void lecr_longest_common_prefix(void) { le_selected_candidate_index = 0; if (le_candidates.length == 0) { lebuf_print_alert(true); } else { bool subst = ctxt->substsrc || need_subst(); if (le_candidates.length > 1) { le_selected_candidate_index = le_candidates.length; update_main_buffer(subst, false); } else { update_main_buffer(subst, true); } } le_complete_cleanup(); } /* Updates `le_selected_candidate_index' by calling `selector' with the argument * `offset'. The `selector' function must update `le_selected_candidate_index' * according to the given `offset'. * The main buffer contents is also updated so that it contains the value of the * new selected candidate. * If there are no candidates, `le_complete' is called to produce candidates. */ void select_candidate(void selector(int offset), int offset) { if (le_candidates.contents == NULL) { le_complete(lecr_normal); return; } else if (le_candidates.length == 0) { return; } selector(offset); update_main_buffer(false, false); } /* Increases `le_selected_candidate_index' by `offset', selecting the `offset'th * next candidate. If there are no candidates, simply calls `le_complete' to * produce candidates. */ void le_complete_select_candidate(int offset) { select_candidate(select_candidate_by_offset, offset); } void select_candidate_by_offset(int offset) { assert(le_selected_candidate_index <= le_candidates.length); if (offset >= 0) { offset %= le_candidates.length + 1; le_selected_candidate_index += offset; le_selected_candidate_index %= le_candidates.length + 1; } else { offset = -offset % (le_candidates.length + 1); if ((size_t) offset <= le_selected_candidate_index) le_selected_candidate_index -= offset; else le_selected_candidate_index += le_candidates.length - offset + 1; } assert(le_selected_candidate_index <= le_candidates.length); } /* Selects the first candidate of the `offset'th next column. * If there are no candidates, simply calls `le_complete' to produce candidates. */ void le_complete_select_column(int offset) { select_candidate(le_display_select_column, offset); } /* Selects the first candidate of the `offset'th next page. * If there are no candidates, simply calls `le_complete' to produce candidates. */ void le_complete_select_page(int offset) { select_candidate(le_display_select_page, offset); } /* If `index' is not positive, performs completion and lists candidates. * Otherwise, substitutes the source word with the `index'th candidate and * cleans up. * Returns true iff the source word was successfully substituted. */ bool le_complete_fix_candidate(int index) { if (le_candidates.contents == NULL) { le_complete(lecr_nop); le_selected_candidate_index = le_candidates.length; le_display_make_rawvalues(); } if (le_candidates.length == 0) { lebuf_print_alert(true); return false; } if (index <= 0) return false; unsigned uindex = (unsigned) index - 1; if (uindex >= le_candidates.length) { lebuf_print_alert(true); return false; } le_selected_candidate_index = uindex; bool subst = ctxt->substsrc; if (!subst) { const le_candidate_T *cand = le_candidates.contents[le_selected_candidate_index]; subst = (matchwcsprefix(cand->origvalue, ctxt->src) == NULL); } update_main_buffer(subst, true); le_complete_cleanup(); return true; } /* Clears the current candidates. */ void le_complete_cleanup(void) { le_display_complete_cleanup(); if (le_candidates.contents != NULL) { plfree(pl_toary(&le_candidates), free_candidate); le_candidates.contents = NULL; } free_context(ctxt); ctxt = NULL; } /* Frees a completion candidate. * The argument must point to a `le_candidate_T' value. */ void free_candidate(void *c) { le_candidate_T *cand = c; free(cand->origvalue); free(cand->rawvalue.raw); free(cand->desc); free(cand->rawdesc.raw); free(cand); } /* Frees the specified `le_context_T' data. */ void free_context(le_context_T *ctxt) { if (ctxt != NULL) { plfree(ctxt->pwords, free); free(ctxt->src); free(ctxt->pattern); free(ctxt); } } /* Sorts the candidates in the candidate list and removes duplicates. */ void sort_candidates(void) { qsort(le_candidates.contents, le_candidates.length, sizeof *le_candidates.contents, sort_candidates_cmp); if (le_candidates.length >= 2) { for (size_t i = le_candidates.length - 1; i > 0; i--) { le_candidate_T *cand1 = le_candidates.contents[i]; le_candidate_T *cand2 = le_candidates.contents[i - 1]; // XXX case-sensitive if (wcscoll(cand1->origvalue, cand2->origvalue) == 0) { free_candidate(cand1); pl_remove(&le_candidates, i, 1); } } } } int sort_candidates_cmp(const void *cp1, const void *cp2) { const le_candidate_T *cand1 = *(const le_candidate_T **) cp1; const le_candidate_T *cand2 = *(const le_candidate_T **) cp2; const wchar_t *v1 = cand1->origvalue; const wchar_t *v2 = cand2->origvalue; /* Candidates that start with hyphens are sorted in a special order so that * short options come before long options. Such candidates are sorted case- * insensitively. */ if (v1[0] == L'-' || v2[0] == L'-') { while (*v1 == L'-' && *v2 == L'-') v1++, v2++; if (*v1 == L'-') return 1; if (*v2 == L'-') return -1; #if HAVE_WCSCASECMP int cmp = wcscasecmp(v1, v2); if (cmp != 0) return cmp; #endif } return wcscoll(v1, v2); // XXX case-sensitive } /* Prints the formatted string to the standard error if the completion debugging * option is on. * The string is preceded by "[compdebug] " and followed by a newline. */ void le_compdebug(const char *format, ...) { if (!le_state_is_compdebug) return; fputs("[compdebug] ", stderr); va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fputc('\n', stderr); } /* Prints information on the specified context if the `compdebug' option is * enabled. */ void print_context_info(const le_context_T *ctxt) { const char *INIT(s, NULL); switch (ctxt->quote) { case QUOTE_NONE: s = "none"; break; case QUOTE_NORMAL: s = "normal"; break; case QUOTE_SINGLE: s = "single"; break; case QUOTE_DOUBLE: s = "double"; break; } le_compdebug("quote type: %s", s); switch (ctxt->type & CTXT_MASK) { case CTXT_NORMAL: s = "normal"; break; case CTXT_COMMAND: s = "command"; break; case CTXT_ARGUMENT: s = "argument"; break; case CTXT_TILDE: s = "tilde"; break; case CTXT_VAR: s = "variable"; break; case CTXT_ARITH: s = "arithmetic"; break; case CTXT_ASSIGN: s = "assignment"; break; case CTXT_REDIR: s = "redirection"; break; case CTXT_REDIR_FD: s = "redirection (fd)"; break; case CTXT_FOR_IN: s = "\"in\" or \"do\""; break; case CTXT_FOR_DO: s = "\"do\""; break; case CTXT_CASE_IN: s = "\"in\""; break; case CTXT_FUNCTION: s = "function name"; break; } le_compdebug("context type: %s%s%s%s", s, ctxt->type & CTXT_EBRACED ? " (in brace expn)" : "", ctxt->type & CTXT_VBRACED ? " (in variable)" : "", ctxt->type & CTXT_QUOTED ? " (quoted)" : ""); for (int i = 0; i < ctxt->pwordc; i++) le_compdebug("preceding word %d: \"%ls\"", i + 1, (const wchar_t *) ctxt->pwords[i]); le_compdebug("target word: \"%ls\"", ctxt->src); le_compdebug(" as pattern: \"%ls\"", ctxt->pattern); } /* Prints information on the specified `compopt' if the `compdebug' option is * enabled. */ void print_compopt_info(const le_compopt_T *compopt) { if (!le_state_is_compdebug) return; le_compdebug("target word without prefix: \"%ls\"", compopt->src); for (const le_comppattern_T *p = compopt->patterns; p != NULL; p = p->next){ const char *INIT(s, NULL); switch (p->type) { case CPT_ACCEPT: s = "accept"; break; case CPT_REJECT: s = "reject"; break; } le_compdebug("pattern: \"%ls\" (%s)", p->pattern, s); } if (compopt->suffix != NULL) le_compdebug("suffix: \"%ls\"", compopt->suffix); if (!compopt->terminate) le_compdebug("completed word will not be terminated"); } /********** Completion Function Execution **********/ /* name of the file that is autoloaded in the first completion */ #define INIT_COMPFILE "completion/INIT" /* completion function names */ #define COMMAND_COMPFUNC "completion//command" #define ARGUMENT_COMPFUNC "completion//argument" /* Loads and executes completion function to generate candidates. */ void execute_completion_function(void) { static bool once = false; if (!once) { once = true; autoload_completion_function_file(L"" INIT_COMPFILE, NULL); } switch (ctxt->type & CTXT_MASK) { case CTXT_NORMAL: case CTXT_ASSIGN: case CTXT_REDIR: simple_completion(CGT_FILE); break; case CTXT_COMMAND: if (!call_completion_function(L"" COMMAND_COMPFUNC)) complete_command_default(); break; case CTXT_ARGUMENT: if (!call_completion_function(L"" ARGUMENT_COMPFUNC)) simple_completion(CGT_FILE); break; case CTXT_TILDE: simple_completion(CGT_LOGNAME | CGT_DIRSTACK); break; case CTXT_VAR: simple_completion(CGT_VARIABLE); break; case CTXT_ARITH: simple_completion(CGT_SCALAR); break; case CTXT_REDIR_FD: break; case CTXT_FOR_IN: word_completion(2, L"in", L"do"); break; case CTXT_FOR_DO: word_completion(1, L"do"); break; case CTXT_CASE_IN: word_completion(1, L"in"); break; case CTXT_FUNCTION: simple_completion(CGT_FUNCTION); break; } } /* Sets special local variables $WORDS and $TARGETWORD in the current variable * environment. Also sets the $IFS variable to the default value. */ void set_completion_variables(void) { set_array(L VAR_WORDS, ctxt->pwordc, pldup(ctxt->pwords, copyaswcs), SCOPE_LOCAL, false); set_variable(L VAR_TARGETWORD, xwcsdup(ctxt->src), SCOPE_LOCAL, false); set_variable(L VAR_IFS, xwcsdup(DEFAULT_IFS), SCOPE_LOCAL, false); } /* Performs command name completion in the default settings. */ void complete_command_default(void) { le_comppattern_T pattern1, pattern2; le_compopt_T compopt; pattern1.type = CPT_ACCEPT; pattern1.pattern = ctxt->pattern; pattern1.cpattern = NULL; pattern2.next = NULL; pattern2.type = CPT_REJECT; pattern2.pattern = L"*/*"; pattern2.cpattern = NULL; compopt.ctxt = ctxt; compopt.src = ctxt->src; compopt.patterns = &pattern1; pattern1.next = NULL; compopt.type = CGT_DIRECTORY; compopt.suffix = L"/"; compopt.terminate = false; print_compopt_info(&compopt); generate_file_candidates(&compopt); compopt.suffix = NULL; compopt.terminate = true; if (wcschr(ctxt->src, L'/') != NULL) { // pattern1.next = NULL; compopt.type = CGT_EXECUTABLE; } else { pattern1.next = &pattern2; compopt.type = CGT_COMMAND; if (ctxt->quote == QUOTE_NORMAL && wcschr(ctxt->pattern, L'\\') == NULL) compopt.type |= CGT_KEYWORD | CGT_NALIAS; } print_compopt_info(&compopt); generate_candidates(&compopt); } /********** Completion Candidate Generation **********/ /* Perform completion for the specified candidate type(s). */ void simple_completion(le_candgentype_T type) { le_comppattern_T pattern = { .type = CPT_ACCEPT, .pattern = ctxt->pattern, }; le_compopt_T compopt = { .ctxt = ctxt, .type = type, .src = ctxt->src, .patterns = &pattern, .suffix = NULL, .terminate = true, }; print_compopt_info(&compopt); generate_candidates(&compopt); } /* Calls all candidate generation functions. * `cpattern's in `compopt->patterns' are freed in this function but are NOT * set to NULL. */ void generate_candidates(const le_compopt_T *compopt) { generate_file_candidates(compopt); generate_builtin_candidates(compopt); generate_external_command_candidates(compopt); generate_function_candidates(compopt); generate_keyword_candidates(compopt); generate_alias_candidates(compopt); generate_variable_candidates(compopt); generate_job_candidates(compopt); generate_signal_candidates(compopt); generate_logname_candidates(compopt); generate_group_candidates(compopt); generate_host_candidates(compopt); generate_bindkey_candidates(compopt); generate_dirstack_candidates(compopt); for (const le_comppattern_T *p = compopt->patterns; p != NULL; p = p->next) xfnm_free(p->cpattern); } /* Adds the specified value as a completion candidate to the candidate list. * The ignored prefix in `ctxt->origsrc' is prepended to the candidate value. * A description for the candidate can be given as `desc', which may be NULL * when no description is provided. * Arguments `value' and `desc' must be freeable strings, which are used as the * candidate value and description, respectively. * This function must NOT be used for a CT_FILE candidate. * If `value' is NULL, this function does nothing (except freeing `desc'). */ void le_new_candidate(le_candtype_T type, wchar_t *value, wchar_t *desc, const le_compopt_T *compopt) { if (value == NULL) { free(desc); return; } if (desc != NULL && (desc[0] == L'\0' || wcscmp(value, desc) == 0)) { /* ignore useless description */ free(desc); desc = NULL; } le_candidate_T *cand = xmalloc(sizeof *cand); cand->type = type; cand->value = value; cand->rawvalue.raw = NULL; cand->rawvalue.width = 0; cand->desc = desc; cand->rawdesc.raw = NULL; cand->rawdesc.width = 0; le_add_candidate(cand, compopt); } /* Adds the specified candidate to the candidate list. * The le_candidate_T structure must have been properly initialized, except for * the `origvalue' and `terminate' members, which are initialized in this * function. * This function treats the prefix and suffix specified in the "complete" * built-in invocation. */ void le_add_candidate(le_candidate_T *cand, const le_compopt_T *compopt) { xwcsbuf_T buf; wb_initwith(&buf, cand->value); /* prepend prefix */ const wchar_t *origsrc = compopt->ctxt->src; size_t prefixlength = compopt->src - origsrc; if (prefixlength != 0) wb_ninsert_force(&buf, 0, origsrc, prefixlength); /* append suffix */ bool allowterminate = true; if ((cand->type == CT_FILE) && S_ISDIR(cand->appendage.filestat.mode) && !(compopt->type & CGT_DIRECTORY)) { wb_wccat(&buf, L'/'); allowterminate = false; } else if (compopt->suffix != NULL) { wb_cat(&buf, compopt->suffix); } cand->origvalue = wb_towcs(&buf); cand->value = &cand->origvalue[prefixlength]; cand->terminate = compopt->terminate && allowterminate; if (le_state_is_compdebug) { const char *typestr = NULL; switch (cand->type) { case CT_WORD: typestr = "word"; break; case CT_FILE: typestr = "file"; break; case CT_COMMAND: typestr = "command"; break; case CT_ALIAS: typestr = "alias"; break; case CT_OPTION: typestr = "option"; break; case CT_VAR: typestr = "variable"; break; case CT_JOB: typestr = "job"; break; case CT_SIG: typestr = "signal"; break; case CT_LOGNAME: typestr = "user name"; break; case CT_GRP: typestr = "group name"; break; case CT_HOSTNAME: typestr = "host name"; break; case CT_BINDKEY: typestr = "lineedit command"; break; } le_compdebug("new %s candidate \"%ls\"", typestr, cand->origvalue); if (cand->desc != NULL) le_compdebug(" (desc: %ls)", cand->desc); if (!cand->terminate) le_compdebug(" (no termination)"); } pl_add(&le_candidates, cand); } /* Compiles `pattern's in `compopt->patterns' into `cpattern's if not yet * compiled. Returns true iff successful. */ bool le_compile_cpatterns(const le_compopt_T *compopt) { for (le_comppattern_T *p = compopt->patterns; p != NULL; p = p->next) { if (p->cpattern != NULL) continue; p->cpattern = xfnm_compile(p->pattern, XFNM_HEADONLY | XFNM_TAILONLY); if (p->cpattern == NULL) { le_compdebug("failed to compile pattern \"%ls\"", p->pattern); return false; } } return true; } /* Perform pattern matching for multibyte string `s' using patterns `ps'. * The patterns must have been compiled. Returns true iff successful. */ bool le_match_patterns(const le_comppattern_T *ps, const char *s) { for (; ps != NULL; ps = ps->next){ bool match = (xfnm_match(ps->cpattern, s) == 0); switch (ps->type) { case CPT_ACCEPT: if (!match) return false; break; case CPT_REJECT: if (match) return false; break; } } return true; } /* Perform pattern matching for multibyte string `s' using patterns in * `compopt->patterns'. The patterns must have been compiled. * Returns true iff successful. */ bool le_match_comppatterns(const le_compopt_T *compopt, const char *s) { return le_match_patterns(compopt->patterns, s); } /* Perform pattern matching for wide string `s' using patterns `ps'. * The patterns must have been compiled. Returns true iff successful. */ bool le_wmatch_patterns(const le_comppattern_T *ps, const wchar_t *s) { for (; ps != NULL; ps = ps->next) { bool match = (xfnm_wmatch(ps->cpattern, s).start != (size_t) -1); switch (ps->type) { case CPT_ACCEPT: if (!match) return false; break; case CPT_REJECT: if (match) return false; break; } } return true; } /* Perform pattern matching for wide string `s' using patterns in * `compopt->patterns'. The patterns must have been compiled. * Returns true iff successful. */ bool le_wmatch_comppatterns(const le_compopt_T *compopt, const wchar_t *s) { return le_wmatch_patterns(compopt->patterns, s); } /* Generates file name candidates. * The CGT_FILE, CGT_DIRECTORY, and CGT_EXECUTABLE flags specify what candidate * to generate. The other flags are ignored. */ void generate_file_candidates(const le_compopt_T *compopt) { if (!(compopt->type & (CGT_FILE | CGT_DIRECTORY | CGT_EXECUTABLE))) return; le_compdebug("adding filename candidates"); if (!le_compile_cpatterns(compopt)) return; enum wglobflags_T flags = 0; // if (shopt_nocaseglob) flags |= WGLB_CASEFOLD; XXX case-sensitive if (shopt_dotglob) flags |= WGLB_PERIOD; if (shopt_extendedglob) flags |= WGLB_RECDIR; const le_comppattern_T *p = compopt->patterns; assert(p->type == CPT_ACCEPT); /* generate candidates by wglob */ plist_T list; wglob(p->pattern, flags, pl_init(&list)); p = p->next; /* check pathnames in `list' and add them to the candidate list */ for (size_t i = 0; i < list.length; i++) { wchar_t *name = list.contents[i]; if (p != NULL) { const wchar_t *basename = wcsrchr(name, L'/'); if (basename == NULL) basename = name; if (!le_wmatch_patterns(p, basename)) { free(name); continue; } } char *mbsname = malloc_wcstombs(name); struct stat st; if (mbsname != NULL && (stat(mbsname, &st) >= 0 || lstat(mbsname, &st) >= 0)) { bool executable = S_ISREG(st.st_mode) && is_executable(mbsname); if ((compopt->type & CGT_FILE) || ((compopt->type & CGT_DIRECTORY) && S_ISDIR(st.st_mode)) || ((compopt->type & CGT_EXECUTABLE) && executable)) { le_candidate_T *cand = xmalloc(sizeof *cand); cand->type = CT_FILE; cand->value = name; cand->rawvalue.raw = NULL; cand->rawvalue.width = 0; cand->desc = NULL; cand->rawdesc.raw = NULL; cand->rawdesc.width = 0; cand->appendage.filestat.is_executable = executable; cand->appendage.filestat.mode = st.st_mode; cand->appendage.filestat.nlink = st.st_nlink; cand->appendage.filestat.size = st.st_size; le_add_candidate(cand, compopt); name = NULL; } } free(name); free(mbsname); } pl_destroy(&list); } /* Generates candidates that are the names of external commands matching the * pattern. * If CGT_EXTCOMMAND is not in `type', this function does nothing. */ void generate_external_command_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_EXTCOMMAND)) return; le_compdebug("adding external command name candidates"); if (!le_compile_cpatterns(compopt)) return; char *const *paths = get_path_array(PA_PATH); xstrbuf_T path; if (paths == NULL) return; sb_init(&path); for (const char *dirpath; (dirpath = *paths) != NULL; paths++) { DIR *dir = opendir(dirpath); struct dirent *de; size_t dirpathlen; if (dir == NULL) continue; sb_cat(&path, dirpath); if (path.length > 0 && path.contents[path.length - 1] != '/') sb_ccat(&path, '/'); dirpathlen = path.length; while ((de = readdir(dir)) != NULL) { if (!le_match_comppatterns(compopt, de->d_name)) continue; sb_cat(&path, de->d_name); if (is_executable_regular(path.contents)) le_new_candidate(CT_COMMAND, malloc_mbstowcs(de->d_name), NULL, compopt); sb_truncate(&path, dirpathlen); } sb_clear(&path); closedir(dir); } sb_destroy(&path); } /* Generates candidates that are keywords matching the pattern. */ void generate_keyword_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_KEYWORD)) return; le_compdebug("adding keyword candidates"); if (!le_compile_cpatterns(compopt)) return; static const wchar_t *keywords[] = { L"case", L"do", L"done", L"elif", L"else", L"esac", L"fi", L"for", L"function", L"if", L"then", L"until", L"while", NULL, // XXX "select" is not currently supported }; for (const wchar_t **k = keywords; *k != NULL; k++) if (le_wmatch_comppatterns(compopt, *k)) le_new_candidate(CT_COMMAND, xwcsdup(*k), NULL, compopt); } /* Generates candidates to complete a user name matching the pattern. */ void generate_logname_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_LOGNAME)) return; le_compdebug("adding user name candidates"); #if HAVE_GETPWENT if (!le_compile_cpatterns(compopt)) return; struct passwd *pwd; setpwent(); while ((pwd = getpwent()) != NULL) if (le_match_comppatterns(compopt, pwd->pw_name)) le_new_candidate(CT_LOGNAME, malloc_mbstowcs(pwd->pw_name), # if HAVE_PW_GECOS (pwd->pw_gecos != NULL) ? malloc_mbstowcs(pwd->pw_gecos) : # endif NULL, compopt); endpwent(); #else le_compdebug(" getpwent not supported on this system"); #endif } /* Generates candidates to complete a group name matching the pattern. */ void generate_group_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_GROUP)) return; le_compdebug("adding group name candidates"); #if HAVE_GETGRENT if (!le_compile_cpatterns(compopt)) return; struct group *grp; setgrent(); while ((grp = getgrent()) != NULL) if (le_match_comppatterns(compopt, grp->gr_name)) le_new_candidate( CT_GRP, malloc_mbstowcs(grp->gr_name), NULL, compopt); endgrent(); #else le_compdebug(" getgrent not supported on this system"); #endif } /* Generates candidates to complete a host name matching the pattern. */ void generate_host_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_HOSTNAME)) return; le_compdebug("adding host name candidates"); #if HAVE_GETHOSTENT if (!le_compile_cpatterns(compopt)) return; struct hostent *host; sethostent(true); while ((host = gethostent()) != NULL) { if (le_match_comppatterns(compopt, host->h_name)) le_new_candidate( CT_HOSTNAME, malloc_mbstowcs(host->h_name), NULL, compopt); if (host->h_aliases != NULL) for (char *const *a = host->h_aliases; *a != NULL; a++) if (le_match_comppatterns(compopt, *a)) le_new_candidate( CT_HOSTNAME, malloc_mbstowcs(*a), NULL, compopt); } endhostent(); #else le_compdebug(" gethostent not supported on this system"); #endif } /* Generates candidates from words that match the pattern. */ void generate_candidates_from_words( le_candtype_T type, void *const *words, const wchar_t *description, const le_compopt_T *compopt) { if (words[0] == NULL) return; le_compdebug("adding specified words"); if (!le_compile_cpatterns(compopt)) return; const wchar_t *word; for (size_t i = 0; (word = words[i]) != NULL; i++) { if (le_wmatch_comppatterns(compopt, word)) le_new_candidate(type, xwcsdup(word), (description == NULL) ? NULL : xwcsdup(description), compopt); } } /* Generates candidates with the specified word values. * The words must be given as (const wchar_t *). * The first argument `count' is the number of the words. */ void word_completion(size_t count, ...) { va_list ap; le_comppattern_T pattern = { .type = CPT_ACCEPT, .pattern = ctxt->pattern, }; le_compopt_T compopt = { .ctxt = ctxt, .type = 0, .src = ctxt->src, .patterns = &pattern, .suffix = NULL, .terminate = true, }; print_compopt_info(&compopt); va_start(ap, count); for (size_t i = 0; i < count; i++) { const wchar_t *word = va_arg(ap, const wchar_t *); if (matchwcsprefix(word, compopt.src)) le_new_candidate(CT_WORD, xwcsdup(word), NULL, &compopt); } va_end(ap); } /********** Displaying Functions **********/ /* Calculates the length of the longest common prefix (leading substring) * for the current candidates. * The result includes the ignored prefix in the candidate values. * The result is saved in `common_prefix_length'. * There must be at least one candidate in `le_candidates'. */ size_t get_common_prefix_length(void) { assert(le_candidates.contents != NULL); assert(le_candidates.length > 0); if (common_prefix_length != (size_t) -1) return common_prefix_length; const le_candidate_T *cand = le_candidates.contents[0]; const wchar_t *value = cand->origvalue; size_t cpl = wcslen(value); for (size_t i = 1; i < le_candidates.length; i++) { cand = le_candidates.contents[i]; const wchar_t *value2 = cand->origvalue; for (size_t j = 0; j < cpl; j++) if (value[j] != value2[j]) // XXX comparison is case-sensitive cpl = j; } common_prefix_length = cpl; if (le_state_is_compdebug) { wchar_t value[common_prefix_length + 1]; wmemcpy(value, cand->origvalue, common_prefix_length); value[common_prefix_length] = L'\0'; le_compdebug("candidate common prefix: \"%ls\"", value); } return common_prefix_length; } /* Inserts the currently selected candidate into the main buffer. * The already inserted candidate is replaced if any. * When no candidate is selected, sets to the longest common prefix of the * candidates. There must be at least one candidate. * If `subst' is true, the whole source word is replaced with the candidate * value. Otherwise, the source word is appended (in which case the word must * have a valid common prefix). * If `finish' is true and if the completed candidate's `terminate' member is * true, the word is closed so that the next word can just be entered directly. * If either `subst' or `finish' is true, the completion state must be cleaned * up after this function. */ void update_main_buffer(bool subst, bool finish) { const le_candidate_T *cand; xwcsbuf_T buf; size_t srclen; size_t substindex; le_quote_T quotetype; wb_init(&buf); if (subst) { srclen = 0; substindex = ctxt->srcindex; quotetype = QUOTE_NORMAL; } else { srclen = wcslen(ctxt->src); substindex = ctxt->origindex; quotetype = ctxt->quote; } if (le_selected_candidate_index >= le_candidates.length) { size_t cpl = get_common_prefix_length(); assert(srclen <= cpl); cand = le_candidates.contents[0]; size_t valuelen = cpl - srclen; wchar_t value[valuelen + 1]; wcsncpy(value, cand->origvalue + srclen, valuelen); value[valuelen] = L'\0'; quote(&buf, value, quotetype); } else { cand = le_candidates.contents[le_selected_candidate_index]; assert(srclen <= wcslen(cand->origvalue)); if (cand->origvalue[0] == L'\0' && quotetype == QUOTE_NORMAL) wb_cat(&buf, L"\"\""); else quote(&buf, cand->origvalue + srclen, quotetype); } assert(le_main_index >= substindex); wb_replace_force(&le_main_buffer, substindex, le_main_index - substindex, buf.contents, buf.length); le_main_index = substindex + buf.length; wb_destroy(&buf); if (le_selected_candidate_index >= le_candidates.length) return; if (!cand->terminate) return; switch (quotetype) { case QUOTE_NONE: case QUOTE_NORMAL: break; case QUOTE_SINGLE: insert_to_main_buffer(L'\''); break; case QUOTE_DOUBLE: insert_to_main_buffer(L'"'); break; } if (!finish) return; if (ctxt->type & CTXT_VBRACED) { insert_to_main_buffer(L'}'); return; } else if (ctxt->type & CTXT_EBRACED) { insert_to_main_buffer(L','); return; } if (ctxt->type & CTXT_QUOTED) { insert_to_main_buffer(L'"'); } switch (ctxt->type & CTXT_MASK) { case CTXT_NORMAL: case CTXT_COMMAND: case CTXT_ARGUMENT: case CTXT_VAR: case CTXT_ARITH: case CTXT_ASSIGN: case CTXT_REDIR: case CTXT_REDIR_FD: case CTXT_FOR_IN: case CTXT_FOR_DO: case CTXT_CASE_IN: case CTXT_FUNCTION: insert_to_main_buffer(L' '); break; case CTXT_TILDE: insert_to_main_buffer(L'/'); break; } } /* Inserts the specified character to the main buffer (`le_main_buffer') at the * current index (`le_main_index'). The index is increased by 1. */ void insert_to_main_buffer(wchar_t c) { wb_ninsert_force(&le_main_buffer, le_main_index, &c, 1); le_main_index += 1; } /* Determines whether the source word should be substituted even if * `ctxt->substsrc' is false. */ /* Returns true if there is a candidate that does not begin with * `ctxt->origsrc'. */ bool need_subst(void) { for (size_t i = 0; i < le_candidates.length; i++) { const le_candidate_T *cand = le_candidates.contents[i]; if (matchwcsprefix(cand->origvalue, ctxt->src) == NULL) return true; } return false; } /* Substitutes the source word in the main buffer with all of the current * candidates. `ctxt' must be a valid context. */ void substitute_source_word_all(void) { le_compdebug("substituting source word with candidate(s)"); /* remove source word */ wb_remove(&le_main_buffer, ctxt->srcindex, le_main_index - ctxt->srcindex); le_main_index = ctxt->srcindex; /* insert candidates */ xwcsbuf_T buf; wb_init(&buf); for (size_t i = 0; i < le_candidates.length; i++) { const le_candidate_T* cand = le_candidates.contents[i]; quote(&buf, cand->origvalue, QUOTE_NORMAL); wb_wccat(&buf, L' '); } wb_ninsert_force(&le_main_buffer, le_main_index, buf.contents, buf.length); le_main_index += buf.length; wb_destroy(&buf); } /* Quotes characters in the specified string that are not treated literally * in quotation mode `quotetype'. * The result is appended to the specified buffer, which must have been * initialized by the caller. */ void quote(xwcsbuf_T *restrict buf, const wchar_t *restrict s, le_quote_T quotetype) { const wchar_t *quotechars = (ctxt->type == CTXT_COMMAND) ? L"=|&;<>()$`\\\"'*?[]#~{}" : L"|&;<>()$`\\\"'*?[]#~{}"; switch (quotetype) { case QUOTE_NONE: wb_cat(buf, s); return; case QUOTE_NORMAL: for (size_t i = 0; s[i] != L'\0'; i++) { if (s[i] == L'\n') { wb_ncat_force(buf, L"'\n'", 3); } else { if (wcschr(quotechars, s[i]) != NULL || iswspace(s[i])) wb_wccat(buf, L'\\'); wb_wccat(buf, s[i]); } } return; case QUOTE_SINGLE: for (size_t i = 0; s[i] != L'\0'; i++) { if (s[i] != L'\'') wb_wccat(buf, s[i]); else wb_ncat_force(buf, L"'\\''", 4); } return; case QUOTE_DOUBLE: for (size_t i = 0; s[i] != L'\0'; i++) { if (wcschr(CHARS_ESCAPABLE, s[i]) != NULL) wb_wccat(buf, L'\\'); wb_wccat(buf, s[i]); } return; } assert(false); } /********** Built-ins **********/ /* Options for the "complete" built-in. */ const struct xgetopt_T complete_options[] = { { L'A', L"accept", OPTARG_REQUIRED, true, NULL, }, { L'a', L"alias", OPTARG_NONE, true, NULL, }, { L'-', L"array-variable", OPTARG_NONE, true, NULL, }, { L'-', L"bindkey", OPTARG_NONE, true, NULL, }, { L'b', L"builtin-command", OPTARG_NONE, true, NULL, }, { L'c', L"command", OPTARG_NONE, true, NULL, }, { L'D', L"description", OPTARG_REQUIRED, true, NULL, }, { L'd', L"directory", OPTARG_NONE, true, NULL, }, { L'-', L"dirstack-index", OPTARG_NONE, true, NULL, }, { L'-', L"executable-file", OPTARG_NONE, true, NULL, }, { L'-', L"external-command", OPTARG_NONE, true, NULL, }, { L'f', L"file", OPTARG_NONE, true, NULL, }, { L'-', L"finished-job", OPTARG_NONE, true, NULL, }, { L'-', L"function", OPTARG_NONE, true, NULL, }, { L'-', L"global-alias", OPTARG_NONE, true, NULL, }, { L'g', L"group", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'h', L"hostname", OPTARG_NONE, true, NULL, }, { L'j', L"job", OPTARG_NONE, true, NULL, }, { L'k', L"keyword", OPTARG_NONE, true, NULL, }, { L'T', L"no-termination", OPTARG_NONE, true, NULL, }, { L'-', L"normal-alias", OPTARG_NONE, true, NULL, }, { L'O', L"option", OPTARG_NONE, true, NULL, }, { L'P', L"prefix", OPTARG_REQUIRED, true, NULL, }, { L'-', L"regular-builtin", OPTARG_NONE, true, NULL, }, { L'R', L"reject", OPTARG_REQUIRED, true, NULL, }, { L'-', L"running-job", OPTARG_NONE, true, NULL, }, { L'-', L"scalar-variable", OPTARG_NONE, true, NULL, }, { L'-', L"semi-special-builtin", OPTARG_NONE, true, NULL, }, { L'-', L"signal", OPTARG_NONE, true, NULL, }, { L'-', L"special-builtin", OPTARG_NONE, true, NULL, }, { L'-', L"stopped-job", OPTARG_NONE, true, NULL, }, { L'S', L"suffix", OPTARG_REQUIRED, true, NULL, }, { L'u', L"username", OPTARG_NONE, true, NULL, }, { L'v', L"variable", OPTARG_NONE, true, NULL, }, { L'\0', NULL, 0, false, NULL, }, }; /* The "complete" built-in. */ int complete_builtin(int argc __attribute__((unused)), void **argv) { const wchar_t *prefix = NULL, *suffix = NULL; const wchar_t *description = NULL; le_candgentype_T cgtype = 0; le_candtype_T candtype = CT_WORD; le_comppattern_T *patterns = NULL; bool terminate = true; #define NEWPATTERN(typ) \ do { \ le_comppattern_T *newpattern = xmalloc(sizeof *newpattern); \ *newpattern = (le_comppattern_T) { \ .next = patterns, .type = typ, .pattern = xoptarg, \ }; \ patterns = newpattern; \ } while (0) int exitstatus; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, complete_options, 0)) != NULL) { switch (opt->shortopt) { case L'A': NEWPATTERN(CPT_ACCEPT); break; case L'a': cgtype |= CGT_ALIAS; break; case L'b': cgtype |= CGT_BUILTIN; break; case L'c': cgtype |= CGT_COMMAND; break; case L'D': if (description != NULL) goto dupopterror; description = xoptarg; break; case L'd': cgtype |= CGT_DIRECTORY; break; case L'f': cgtype |= CGT_FILE; break; case L'g': cgtype |= CGT_GROUP; break; case L'h': cgtype |= CGT_HOSTNAME; break; case L'j': cgtype |= CGT_JOB; break; case L'k': cgtype |= CGT_KEYWORD; break; case L'O': candtype = CT_OPTION; break; case L'P': if (prefix != NULL) goto dupopterror; prefix = xoptarg; break; case L'R': NEWPATTERN(CPT_REJECT); break; case L'S': if (suffix != NULL) goto dupopterror; suffix = xoptarg; break; case L'T': terminate = false; break; case L'u': cgtype |= CGT_LOGNAME; break; case L'v': cgtype |= CGT_VARIABLE; break; case L'-': switch (opt->longopt[0]) { case L'a': cgtype |= CGT_ARRAY; break; case L'b': cgtype |= CGT_BINDKEY; break; case L'd': cgtype |= CGT_DIRSTACK; break; case L'e': assert(opt->longopt[1] == L'x'); switch (opt->longopt[2]) { case L'e': cgtype |= CGT_EXECUTABLE; break; case L't': cgtype |= CGT_EXTCOMMAND; break; default: assert(false); } break; case L'f': switch (opt->longopt[1]) { case L'i': cgtype |= CGT_DONE; break; case L'u': cgtype |= CGT_FUNCTION; break; default: assert(false); } break; case L'g': cgtype |= CGT_GALIAS; break; #if YASH_ENABLE_HELP case L'h': exitstatus = print_builtin_help(ARGV(0)); goto finish; #endif case L'n': cgtype |= CGT_NALIAS; break; case L'r': switch (opt->longopt[1]) { case L'e': cgtype |= CGT_RBUILTIN; break; case L'u': cgtype |= CGT_RUNNING; break; default: assert(false); } break; case L's': switch (opt->longopt[1]) { case L'c': cgtype |= CGT_SCALAR; break; case L'e': cgtype |= CGT_SSBUILTIN; break; case L'i': cgtype |= CGT_SIGNAL; break; case L'p': cgtype |= CGT_SBUILTIN; break; case L't': cgtype |= CGT_STOPPED; break; default: assert(false); } break; default: assert(false); } break; dupopterror: xerror(0, Ngt("more than one -%lc option is specified"), (wint_t) opt->shortopt); /* falls thru */ default: exitstatus = Exit_ERROR; goto finish; } } #undef NEWPATTERN if (ctxt == NULL) { xerror(0, Ngt("the complete built-in can be used " "during command line completion only")); exitstatus = Exit_ERROR; goto finish; } void *const *words = &argv[xoptind]; /* treat `prefix' */ const wchar_t *src = ctxt->src, *pattern = ctxt->pattern; if (prefix != NULL) { src = matchwcsprefix(src, prefix); if (src == NULL) { xerror(0, Ngt("the specified prefix `%ls' does not match " "the target word `%ls'"), prefix, ctxt->src); exitstatus = Exit_ERROR; goto finish; } while (*prefix != L'\0') { if (*pattern == L'\\') pattern++; assert(*pattern != L'\0'); pattern++; prefix++; } } le_comppattern_T comppatterns = { .next = patterns, .type = CPT_ACCEPT, .pattern = pattern, }; le_compopt_T compopt = { .ctxt = ctxt, .type = cgtype, .src = src, .patterns = &comppatterns, .suffix = suffix, .terminate = terminate, }; print_compopt_info(&compopt); size_t oldcount = le_candidates.length; generate_candidates_from_words(candtype, words, description, &compopt); generate_candidates(&compopt); size_t newcount = le_candidates.length; exitstatus = (oldcount != newcount) ? Exit_SUCCESS : Exit_FAILURE; finish: while (patterns != NULL) { le_comppattern_T *next = patterns->next; free(patterns); patterns = next; } return exitstatus; } #if YASH_ENABLE_HELP const char complete_help[] = Ngt( "generate completion candidates" ); const char complete_syntax[] = Ngt( "\tcomplete [-A pattern] [-R pattern] [-T] [-P prefix] [-S suffix] \\\n" "\t [-abcdfghjkuv] [[-O] [-D description] words...]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/complete.h000066400000000000000000000241471354143602500162050ustar00rootroot00000000000000/* Yash: yet another shell */ /* complete.h: command line completion */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_COMPLETE_H #define YASH_COMPLETE_H #include #include #include "../plist.h" #include "../xgetopt.h" typedef enum le_candtype_T { CT_WORD, // normal word CT_FILE, // file name CT_COMMAND, // command name CT_ALIAS, // alias name CT_OPTION, // command option CT_VAR, // variable name CT_JOB, // job name CT_SIG, // signal name CT_LOGNAME, // user name CT_GRP, // group name CT_HOSTNAME, // host name CT_BINDKEY, // line-editing command name } le_candtype_T; typedef struct le_rawvalue_T { char *raw; // pre-printed version of candidate value/description int width; // screen width of `raw' } le_rawvalue_T; typedef struct le_candidate_T { le_candtype_T type; wchar_t *value; // candidate value without ignored prefix wchar_t *origvalue; // candidate value including ignored prefix le_rawvalue_T rawvalue; wchar_t *desc; // candidate description le_rawvalue_T rawdesc; _Bool terminate; // if completed word should be terminated union { struct { _Bool is_executable; mode_t mode; nlink_t nlink; off_t size; } filestat; // only used for CT_FILE } appendage; } le_candidate_T; typedef enum le_quote_T { QUOTE_NONE, // no quotation required QUOTE_NORMAL, // not in single/double quotation; backslashes can be used QUOTE_SINGLE, // in single quotation QUOTE_DOUBLE, // in double quotation } le_quote_T; typedef enum le_contexttype_T { CTXT_NORMAL, // normal word CTXT_COMMAND, // command word CTXT_ARGUMENT, // command argument word CTXT_TILDE, // tilde expansion CTXT_VAR, // variable name CTXT_ARITH, // arithmetic expansion CTXT_ASSIGN, // assignment CTXT_REDIR, // redirection target (that is a file name) CTXT_REDIR_FD, // redirection target (that is a file descriptor) CTXT_FOR_IN, // where keyword "in" or "do" is expected CTXT_FOR_DO, // where keyword "do" is expected CTXT_CASE_IN, // where keyword "in" is expected CTXT_FUNCTION, // where a function name is expected CTXT_MASK = ((1 << 4) - 1), CTXT_EBRACED = 1 << 4, // completion occurs in brace expansion CTXT_VBRACED = 1 << 5, // completion occurs in variable expansion CTXT_QUOTED = 1 << 6, // unquote after completion } le_contexttype_T; typedef struct le_context_T { le_quote_T quote; le_contexttype_T type; int pwordc; // number of `pwords' (non-negative) void **pwords; // words preceding the source word wchar_t *src; // source word wchar_t *pattern; // source word as a matching pattern size_t srcindex; // start index of source word size_t origindex; // original `le_main_index' _Bool substsrc; // substitute source word with candidates? } le_context_T; /* The `pwords' member is an array of pointers to wide strings containing the * expanded words preceding the source word. * The `src' member is the source word expanded by the four expansions, brace * expansion, word splitting, and quote removal. * The `pattern' member is like `src', but differs in that it may contain * backslash escapes and that it may have an additional asterisk at the end * to make it a pattern. * The `srcindex' member designates where the source word starts in the edit * line. The `origindex' member is the value of `le_main_index' before starting * completion. * The `substsrc' member designates whether the source word should be * substituted with obtained candidates. The value is true if and only if the * source word after word splitting is not a globbing pattern, in which case an * asterisk is appended to the source word to make it a pattern. */ /* If the source word is field-split into more than one word, the words but the * last are included in `pwords'. */ /* Examples: * For the command line of "foo --bar='x", the completion parser function * `le_get_context' returns: * `quote' = QUOTE_SINGLE * `type' = CTXT_NORMAL * `pwordc' = 1 * `pwords' = { L"foo" } * `src', = L"--bar=x" * `pattern', = L"--bar=\\x" * `srcindex' = 4 * `origindex' = 12 * `substsrc' = false */ typedef enum le_candgentype_T { CGT_FILE = 1 << 0, // file of any kind CGT_DIRECTORY = 1 << 1, // directory CGT_EXECUTABLE = 1 << 2, // executable file CGT_SBUILTIN = 1 << 3, // special builtin CGT_SSBUILTIN = 1 << 4, // semi-special builtin CGT_RBUILTIN = 1 << 5, // regular builtin CGT_BUILTIN = CGT_SBUILTIN | CGT_SSBUILTIN | CGT_RBUILTIN, CGT_EXTCOMMAND = 1 << 6, // external command CGT_FUNCTION = 1 << 7, // function CGT_COMMAND = CGT_BUILTIN | CGT_EXTCOMMAND | CGT_FUNCTION, CGT_KEYWORD = 1 << 8, // shell keyword CGT_NALIAS = 1 << 9, // non-global alias CGT_GALIAS = 1 << 10, // global alias CGT_ALIAS = CGT_NALIAS | CGT_GALIAS, CGT_SCALAR = 1 << 11, // scalar variable CGT_ARRAY = 1 << 12, // array variable CGT_VARIABLE = CGT_SCALAR | CGT_ARRAY, CGT_RUNNING = 1 << 13, // running job CGT_STOPPED = 1 << 14, // stopped job CGT_DONE = 1 << 15, // finished job CGT_JOB = CGT_RUNNING | CGT_STOPPED | CGT_DONE, CGT_SIGNAL = 1 << 16, // signal name CGT_LOGNAME = 1 << 17, // login user name CGT_GROUP = 1 << 18, // group name CGT_HOSTNAME = 1 << 19, // host name CGT_BINDKEY = 1 << 20, // line-editing command name CGT_DIRSTACK = 1 << 21, // directory stack entry } le_candgentype_T; typedef struct le_comppattern_T { struct le_comppattern_T *next; enum { CPT_ACCEPT, CPT_REJECT, } type; const wchar_t *pattern; // pattern (not including ignored prefix) struct xfnmatch_T *cpattern; // compiled pattern } le_comppattern_T; typedef struct le_compopt_T { const le_context_T *ctxt; // completion context le_candgentype_T type; // type of generated candidates const wchar_t *src; // `ctxt->src' + wcslen(ignored prefix) le_comppattern_T *patterns; // patterns to be matched to candidates const wchar_t *suffix; // string appended to candidate values _Bool terminate; // whether completed word should be terminated } le_compopt_T; /* The `patterns' member of the `le_compopt_T' structure must not be NULL and * the first element of the `le_comppattern_T' linked list must be of type * CPT_ACCEPT. */ typedef void le_compresult_T(void); extern plist_T le_candidates; extern size_t le_selected_candidate_index; extern void le_complete(le_compresult_T lecr); extern void lecr_nop(void); extern void lecr_normal(void); extern void lecr_substitute_all_candidates(void); extern void lecr_longest_common_prefix(void); extern void le_complete_select_candidate(int offset); extern void le_complete_select_column(int offset); extern void le_complete_select_page(int offset); extern _Bool le_complete_fix_candidate(int index); extern void le_complete_cleanup(void); extern void le_compdebug(const char *format, ...) __attribute__((nonnull,format(printf,1,2))); extern void set_completion_variables(void); extern void le_new_command_candidate(wchar_t *cmdname) __attribute__((nonnull)); extern void le_new_candidate(le_candtype_T type, wchar_t *restrict value, wchar_t *restrict desc, const le_compopt_T *compopt) __attribute__((nonnull(4))); extern void le_add_candidate(le_candidate_T *cand, const le_compopt_T *compopt) __attribute__((nonnull)); extern _Bool le_compile_cpatterns(const le_compopt_T *compopt) __attribute__((nonnull)); extern _Bool le_match_comppatterns(const le_compopt_T *compopt, const char *s) __attribute__((nonnull)); extern _Bool le_wmatch_comppatterns( const le_compopt_T *compopt, const wchar_t *s) __attribute__((nonnull)); extern int complete_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char complete_help[], complete_syntax[]; #endif extern const struct xgetopt_T complete_options[]; /* This function is defined in "../alias.c". */ extern void generate_alias_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); /* This function is defined in "../builtin.c". */ extern void generate_builtin_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); /* This function is defined in "../job.c". */ extern void generate_job_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); /* This function is defined in "../sig.c". */ extern void generate_signal_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); /* These functions are defined in "../variable.c". */ extern void generate_variable_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); extern void generate_function_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); extern void generate_dirstack_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); /* This function is defined in "keymap.c". */ extern void generate_bindkey_candidates(const le_compopt_T *compopt) __attribute__((nonnull)); #endif /* YASH_COMPLETE_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/compparse.c000066400000000000000000001024201354143602500163500ustar00rootroot00000000000000/* Yash: yet another shell */ /* compparse.c: simple parser for command line completion */ /* (C) 2007-2017 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "compparse.h" #include #include #include #include #include #include "../alias.h" #include "../expand.h" #include "../option.h" #include "../parser.h" #include "../plist.h" #include "../strbuf.h" #include "../util.h" #include "../variable.h" #include "../xfnmatch.h" #include "complete.h" #include "editing.h" #include "lineedit.h" /* This structure contains data used in parsing */ typedef struct cparseinfo_T { xwcsbuf_T buf; size_t bufindex; struct aliaslist_T *aliaslist; le_context_T *ctxt; } cparseinfo_T; /* The `buf' buffer contains the first `le_main_index' characters of the edit * buffer. During parsing, alias substitution may be performed on this buffer. * The `bufindex' index indicates the point the parser is currently parsing. * The `ctxt' member points to the structure in which the final result is saved. */ /* This structure contains data used during parsing */ static cparseinfo_T *pi; #define BUF (pi->buf.contents) #define LEN (pi->buf.length) #define INDEX (pi->bufindex) static void empty_pwords(void); static void set_pwords(plist_T *pwords) __attribute__((nonnull)); static bool cparse_commands(void); static void skip_blanks(void); static void skip_blanks_and_newlines(void); static bool csubstitute_alias(substaliasflags_T flags); static bool cparse_command(void); static bool has_token(const wchar_t *token) __attribute__((nonnull,pure)); static bool cparse_simple_command(void); static bool cparse_redirections(void); static bool ctryparse_assignment(void); static bool ctryparse_redirect(void); static bool cparse_for_command(void); static bool cparse_case_command(void); static bool cparse_function_definition(void); static wchar_t *cparse_and_expand_word( tildetype_T tilde, le_contexttype_T ctxttype) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_word( bool testfunc(wchar_t c), tildetype_T tilde, le_contexttype_T ctxttype) __attribute__((nonnull,malloc,warn_unused_result)); static bool ctryparse_tilde(void); static wordunit_T *cparse_special_word_unit(le_contexttype_T ctxttype) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_paramexp_raw(le_contexttype_T ctxttype) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_paramexp_in_brace(le_contexttype_T ctxttype) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_arith(void) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_cmdsubst_in_paren(void) __attribute__((malloc,warn_unused_result)); static wordunit_T *cparse_cmdsubst_in_backquote(void) __attribute__((malloc,warn_unused_result)); static bool is_slash_or_closing_brace(wchar_t c) __attribute__((const)); static bool is_closing_brace(wchar_t c) __attribute__((const)); static bool is_closing_bracket(wchar_t c) __attribute__((const)); static bool is_arith_delimiter(wchar_t c) __attribute__((pure)); static bool remove_braceexpand(wchar_t *s) __attribute__((nonnull)); static bool remove_braceexpand_inner(xwcsbuf_T *buf, size_t *index) __attribute__((nonnull)); /* Parses the contents of the edit buffer (`le_main_buffer') from the beginning * up to the current cursor position (`le_main_index') and determines the * current completion context. * The results are returned as a newly malloced `le_context_T' data. */ le_context_T *le_get_context(void) { assert(wcslen(le_main_buffer.contents) == le_main_buffer.length); le_context_T *ctxt = xmalloc(sizeof *ctxt); cparseinfo_T parseinfo; wb_init(&parseinfo.buf); wb_ncat_force(&parseinfo.buf, le_main_buffer.contents, le_main_index); parseinfo.bufindex = 0; parseinfo.aliaslist = NULL; parseinfo.ctxt = ctxt; pi = &parseinfo; while (!cparse_commands()) parseinfo.bufindex++; #ifndef NDEBUG pi = NULL; #endif wb_destroy(&parseinfo.buf); destroy_aliaslist(parseinfo.aliaslist); if (shopt_braceexpand) if (remove_braceexpand(ctxt->pattern)) ctxt->type |= CTXT_EBRACED; ctxt->src = unescape(ctxt->pattern); if (is_pathname_matching_pattern(ctxt->pattern)) { ctxt->substsrc = true; } else { ctxt->substsrc = false; xwcsbuf_T buf; wb_initwith(&buf, ctxt->pattern); wb_wccat(&buf, L'*'); ctxt->pattern = wb_towcs(&buf); } ctxt->origindex = le_main_index; return ctxt; } /* If `pi->ctxt->pwords' is NULL, assigns a new empty list to it. */ void empty_pwords(void) { if (pi->ctxt->pwords == NULL) { pi->ctxt->pwordc = 0; pi->ctxt->pwords = xmalloc(1 * sizeof *pi->ctxt->pwords); pi->ctxt->pwords[0] = NULL; } } /* Sets `pi->ctxt->pwords' to the contents of `pwords'. */ void set_pwords(plist_T *pwords) { if (pi->ctxt->pwords == NULL) { pi->ctxt->pwordc = pwords->length; pi->ctxt->pwords = pl_toary(pwords); } else { plfree(pl_toary(pwords), free); } } /* Following parser functions return true iff parsing is finished and the result * is saved in the context structure `pi->ctxt'. * The result is saved in the following variables of the context structure: * quote, type, pwordc, pwords, pattern, srcindex. */ /* Parses commands from the current position until a right parenthesis (")") is * found or the whole line is parsed. */ bool cparse_commands(void) { for (;;) { skip_blanks(); switch (BUF[INDEX]) { case L'\n': case L';': case L'&': case L'|': INDEX++; continue; case L')': return false; } if (cparse_command()) return true; } } /* Skips blank characters. */ void skip_blanks(void) { while (iswblank(BUF[INDEX])) INDEX++; } /* Skips blank characters and newlines. */ void skip_blanks_and_newlines(void) { while (BUF[INDEX] == L'\n' || iswblank(BUF[INDEX])) INDEX++; } /* Performs alias substitution at the current position. * See the description of `substitute_alias' for the usage of `flags'. * Returns true iff any alias is substituted. * If the word at the current INDEX is at the end of BUF, the word is not * substituted because it is the word being completed. */ bool csubstitute_alias(substaliasflags_T flags) { return substitute_alias(&pi->buf, INDEX, &pi->aliaslist, flags | AF_NOEOF); } /* Parses a command from the current position. */ bool cparse_command(void) { if (BUF[INDEX] == L'(') { INDEX++; if (cparse_commands()) return true; assert(BUF[INDEX] == L')'); INDEX++; return cparse_redirections(); } else if (has_token(L"{") || has_token(L"!")) { INDEX++; return false; } else if (has_token(L"if") || has_token(L"do")) { INDEX += 2; return false; } else if (has_token(L"then") || has_token(L"else") || has_token(L"elif")) { INDEX += 4; return false; } else if (has_token(L"while") || has_token(L"until")) { INDEX += 5; return false; } else if (has_token(L"}")) { INDEX++; return cparse_redirections(); } else if (has_token(L"fi")) { INDEX += 2; return cparse_redirections(); } else if (has_token(L"done") || has_token(L"esac")) { INDEX += 4; return cparse_redirections(); } else if (has_token(L"for")) { return cparse_for_command(); } else if (has_token(L"case")) { return cparse_case_command(); } else if (!posixly_correct && has_token(L"function")) { return cparse_function_definition(); } else { return cparse_simple_command(); } } /* Returns true iff the specified word is at the current position (and it is not * at the end of the buffer). */ bool has_token(const wchar_t *token) { const wchar_t *c = matchwcsprefix(BUF + INDEX, token); return c != NULL && *c != L'\0' && is_token_delimiter_char(*c); } /* Parses a simple command. */ bool cparse_simple_command(void) { cparse_simple_command: if (csubstitute_alias(AF_NONGLOBAL)) return false; size_t saveindex; skip_blanks(); saveindex = INDEX; if (ctryparse_redirect()) return true; if (saveindex != INDEX) { skip_blanks(); goto cparse_simple_command; } if (ctryparse_assignment()) return true; if (saveindex != INDEX) { skip_blanks(); goto cparse_simple_command; } plist_T pwords; pl_init(&pwords); for (;;) { switch (BUF[INDEX]) { case L'\n': case L';': case L'&': case L'|': case L'(': case L')': plfree(pl_toary(&pwords), free); return false; } wordunit_T *w = cparse_word(is_token_delimiter_char, TT_SINGLE, pwords.length == 0 ? CTXT_COMMAND : CTXT_ARGUMENT); if (w == NULL) { set_pwords(&pwords); return true; } else { expand_multiple(w, &pwords); wordfree(w); } do { skip_blanks(); if (ctryparse_redirect()) { plfree(pl_toary(&pwords), free); return true; } skip_blanks(); } while (csubstitute_alias(0)); } } /* Parses redirections as many as possible. * Global aliases are substituted if necessary. */ bool cparse_redirections(void) { for (;;) { skip_blanks(); size_t saveindex = INDEX; if (ctryparse_redirect()) return true; if (saveindex == INDEX) { skip_blanks(); if (!csubstitute_alias(0)) return false; } } } /* Parses an assignment if any. * `skip_blanks' should be called before this function is called. */ bool ctryparse_assignment(void) { size_t index = INDEX; if (BUF[index] == L'=' || iswdigit(BUF[index])) return false; while (is_name_char(BUF[index])) index++; if (BUF[index] != L'=') return false; INDEX = index + 1; if (BUF[INDEX] != L'(') { /* scalar variable */ wchar_t *value = cparse_and_expand_word(TT_MULTI, CTXT_ASSIGN); if (value == NULL) { empty_pwords(); return true; } else { free(value); return false; } } else { /* parse array contents */ plist_T pwords; pl_init(&pwords); INDEX++; for (;;) { do skip_blanks(); while (csubstitute_alias(0)); if (BUF[INDEX] != L'\0' && is_token_delimiter_char(BUF[INDEX])) { if (BUF[INDEX] == L')') INDEX++; plfree(pl_toary(&pwords), free); return false; } wordunit_T *w = cparse_word( is_token_delimiter_char, TT_SINGLE, CTXT_ASSIGN); if (w == NULL) { set_pwords(&pwords); return true; } else { expand_multiple(w, &pwords); wordfree(w); } } } } /* Parses a redirection if any. * `skip_blanks' should be called before this function is called. */ bool ctryparse_redirect(void) { size_t index = INDEX; le_contexttype_T type; bool result; wchar_t *value; while (iswdigit(BUF[index])) index++; switch (BUF[index]) { case L'<': switch (BUF[index + 1]) { case L'<': type = CTXT_REDIR; switch (BUF[index + 2]) { case L'<': /* here-string */ case L'-': /* here-document */ INDEX = index + 3; break; default: /* here-document */ INDEX = index + 2; break; } break; case L'>': INDEX = index + 2; type = CTXT_REDIR; break; case L'&': INDEX = index + 2; type = CTXT_REDIR_FD; break; case L'(': INDEX = index + 2; goto parse_inner; default: INDEX = index + 1; type = CTXT_REDIR; break; } break; case L'>': switch (BUF[index + 1]) { case L'>': case L'|': INDEX = index + 2; type = CTXT_REDIR; break; case L'(': INDEX = index + 2; goto parse_inner; case L'&': INDEX = index + 2; type = CTXT_REDIR_FD; break; default: INDEX = index + 1; type = CTXT_REDIR; break; } break; default: /* not a redirection */ return false; } skip_blanks(); value = cparse_and_expand_word(TT_SINGLE, type); if (value == NULL) { empty_pwords(); return true; } else { free(value); return false; } parse_inner: result = cparse_commands(); if (!result) { assert(BUF[INDEX] == L')'); INDEX++; } return result; } /* Parses a for command. * There must be the "for" keyword at the current position. */ bool cparse_for_command(void) { assert(wcsncmp(&BUF[INDEX], L"for", 3) == 0); INDEX += 3; do skip_blanks(); while (csubstitute_alias(0)); /* parse variable name */ wordunit_T *w = cparse_word(is_token_delimiter_char, TT_NONE, CTXT_VAR); if (w == NULL) { empty_pwords(); return true; } wordfree(w); skip_blanks_and_newlines(); /* parse "in" ... */ bool in = has_token(L"in"); if (in) { plist_T pwords; pl_init(&pwords); INDEX += 2; for (;;) { do skip_blanks(); while (csubstitute_alias(0)); if (BUF[INDEX] == L';' || BUF[INDEX] == L'\n') { plfree(pl_toary(&pwords), free); INDEX++; break; } if (BUF[INDEX] != L'\0' && is_token_delimiter_char(BUF[INDEX])) { plfree(pl_toary(&pwords), free); return false; } w = cparse_word(is_token_delimiter_char, TT_SINGLE, CTXT_NORMAL); if (w == NULL) { set_pwords(&pwords); return true; } else { expand_multiple(w, &pwords); wordfree(w); } } skip_blanks_and_newlines(); } /* parse "do" ... */ if (has_token(L"do")) { INDEX += 2; return false; } /* found no "in" or "do", so the next word should be one of them */ w = cparse_word(is_token_delimiter_char, TT_NONE, in ? CTXT_FOR_DO : CTXT_FOR_IN); if (w == NULL) { empty_pwords(); return true; } wordfree(w); /* syntax error */ return false; } /* Parses a case command. * There must be the "case" keyword at the current position. */ bool cparse_case_command(void) { assert(wcsncmp(&BUF[INDEX], L"case", 4) == 0); INDEX += 4; do skip_blanks(); while (csubstitute_alias(0)); /* parse matched word */ wordunit_T *w = cparse_word(is_token_delimiter_char, TT_SINGLE, CTXT_NORMAL); if (w == NULL) { empty_pwords(); return true; } wordfree(w); skip_blanks_and_newlines(); /* parse "in" */ if (has_token(L"in")) { INDEX += 2; return false; } /* there is no "in", so the next word should be "in". */ w = cparse_word(is_token_delimiter_char, TT_NONE, CTXT_CASE_IN); if (w == NULL) { empty_pwords(); return true; } wordfree(w); /* syntax error */ return false; } /* Parses a function definition command. * There must be the "function" keyword at the current position. */ bool cparse_function_definition(void) { assert(wcsncmp(&BUF[INDEX], L"function", 8) == 0); INDEX += 8; skip_blanks(); /* parse the function name */ wordunit_T *w = cparse_word(is_token_delimiter_char, TT_NONE, CTXT_FUNCTION); if (w == NULL) { empty_pwords(); return true; } wordfree(w); return false; } /* Parses the word at the current position. * `skip_blanks' should be called before this function is called. * The `ctxttype' parameter is the context type of the word parsed. * If the word was completely parsed, the word is expanded until quote removal * and returned as a newly-malloced string. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member. */ wchar_t *cparse_and_expand_word(tildetype_T tilde, le_contexttype_T ctxttype) { wordunit_T *w = cparse_word(is_token_delimiter_char, tilde, ctxttype); if (w == NULL) { return NULL; } else { wchar_t *s = expand_single(w, tilde, true, false); wordfree(w); assert(s != NULL); return s; } } /* Parses the word at the current position. * `skip_blanks' should be called before this function is called. * `testfunc' is a function that determines if a character is a word delimiter. * The `ctxttype' parameter is the context type of the word parsed. * If the word was completely parsed, the word is returned. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member. */ wordunit_T *cparse_word( bool testfunc(wchar_t c), tildetype_T tilde, le_contexttype_T ctxttype) { cparse_word: if (tilde != TT_NONE) if (ctryparse_tilde()) return NULL; wordunit_T *first = NULL, **lastp = &first; bool indq = false; /* in double quotes? */ size_t startindex = INDEX; size_t srcindex = le_main_index - (LEN - INDEX); #define MAKE_WORDUNIT_STRING \ do { \ wordunit_T *w = xmalloc(sizeof *w); \ w->next = NULL; \ w->wu_type = WT_STRING; \ w->wu_string = xwcsndup(&BUF[startindex], INDEX - startindex); \ *lastp = w, lastp = &w->next; \ } while (0) while (indq || !testfunc(BUF[INDEX])) { wordunit_T *wu; switch (BUF[INDEX]) { case L'\0': goto done; case L'\\': if (BUF[INDEX + 1] != L'\0') { INDEX += 2; continue; } break; case L'$': MAKE_WORDUNIT_STRING; wu = cparse_special_word_unit( (ctxttype & CTXT_MASK) | (indq ? CTXT_QUOTED : 0)); if (wu == NULL) { if (pi->ctxt->pwords == NULL && (pi->ctxt->type & CTXT_VBRACED)) { xwcsbuf_T buf; wchar_t *prefix = expand_single(first, tilde, true, false); assert(prefix != NULL); pi->ctxt->pattern = wb_towcs(wb_catfree( wb_initwith(&buf, prefix), pi->ctxt->pattern)); pi->ctxt->srcindex = srcindex; } wordfree(first); return NULL; } *lastp = wu, lastp = &wu->next; startindex = INDEX; continue; case L'`': wu = cparse_cmdsubst_in_backquote(); if (wu == NULL) { wordfree(first); return NULL; } *lastp = wu, lastp = &wu->next; startindex = INDEX; continue; case L'\'': if (!indq) { const wchar_t *end = wcschr(&BUF[INDEX + 1], L'\''); if (end == NULL) goto end_single_quote; INDEX = end - BUF; } break; case L'"': indq = !indq; break; case L':': if (!indq && tilde == TT_MULTI) { INDEX++; wordfree(first); goto cparse_word; } break; } INDEX++; } done: MAKE_WORDUNIT_STRING; if (BUF[INDEX] != L'\0') { assert(first != NULL); return first; } else { pi->ctxt->quote = indq ? QUOTE_DOUBLE : QUOTE_NORMAL; goto finish; } /* if the word ends without a closing quote, add one */ end_single_quote:; wordunit_T *w = xmalloc(sizeof *w); w->next = NULL; w->wu_type = WT_STRING; w->wu_string = malloc_wprintf(L"%ls'", &BUF[startindex]); *lastp = w, lastp = &w->next; pi->ctxt->quote = QUOTE_SINGLE; finish: pi->ctxt->type = ctxttype; pi->ctxt->pwordc = 0; pi->ctxt->pwords = NULL; pi->ctxt->pattern = expand_single(first, tilde, true, false); pi->ctxt->srcindex = srcindex; wordfree(first); return NULL; } /* Parses a tilde expansion at the current position if any. * If there is a tilde expansion to complete, this function returns true after * setting the members of `pi->ctxt'. (However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member.) * Otherwise, this function simply returns false. */ bool ctryparse_tilde(void) { if (BUF[INDEX] != L'~') return false; size_t index = INDEX; do { index++; switch (BUF[index]) { case L' ': case L'\t': case L'\n': case L';': case L'&': case L'|': case L'<': case L'>': case L'(': case L')': case L'$': case L'`': case L'/': case L':': case L'\\': case L'\'': case L'"': case L'?': case L'*': case L'[': return false; } if (iswblank(BUF[index])) return false; } while (BUF[index] != L'\0'); pi->ctxt->quote = QUOTE_NONE; pi->ctxt->type = CTXT_TILDE; pi->ctxt->pwordc = 0; pi->ctxt->pwords = NULL; pi->ctxt->pattern = xwcsdup(&BUF[INDEX + 1]); pi->ctxt->srcindex = le_main_index - (LEN - (INDEX + 1)); return true; } /* Parses a parameter expansion or command substitution that starts with '$'. * The `ctxttype' parameter is the context type of the word containing this * expansion. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member. */ wordunit_T *cparse_special_word_unit(le_contexttype_T ctxttype) { assert(BUF[INDEX] == L'$'); switch (BUF[INDEX + 1]) { case L'{': INDEX++; return cparse_paramexp_in_brace(ctxttype); case L'(': if (BUF[INDEX + 2] == L'(') return cparse_arith(); else return cparse_cmdsubst_in_paren(); default: return cparse_paramexp_raw(ctxttype); } } /* Parses a parameter that is not enclosed by { }. * The `ctxttype' parameter is the context type of the word containing this * parameter expansion. Only the CTXT_QUOTED flag in `ctxttype' affect the * result. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member. */ wordunit_T *cparse_paramexp_raw(le_contexttype_T ctxttype) { assert(BUF[INDEX] == L'$'); size_t namelen; switch (BUF[INDEX + 1]) { case L'@': case L'*': case L'#': case L'?': case L'-': case L'$': case L'!': namelen = 1; break; default: if (iswdigit(BUF[INDEX + 1])) { namelen = 1; } else { namelen = 0; while (is_portable_name_char(BUF[INDEX + 1 + namelen])) namelen++; if (BUF[INDEX + 1 + namelen] == L'\0') { /* complete variable name */ pi->ctxt->quote = QUOTE_NONE; pi->ctxt->type = CTXT_VAR | (ctxttype & CTXT_QUOTED); pi->ctxt->pwordc = 0; pi->ctxt->pwords = xmalloc(1 * sizeof *pi->ctxt->pwords); pi->ctxt->pwords[0] = NULL; pi->ctxt->pattern = xwcsndup(&BUF[INDEX + 1], namelen); pi->ctxt->srcindex = le_main_index - namelen; return NULL; } } break; } wordunit_T *wu = xmalloc(sizeof *wu); wu->next = NULL; if (namelen == 0) { wu->wu_type = WT_STRING; wu->wu_string = xwcsdup(L"$"); } else { wu->wu_type = WT_PARAM; wu->wu_param = xmalloc(sizeof *wu->wu_param); wu->wu_param->pe_type = PT_MINUS; wu->wu_param->pe_name = xwcsndup(&BUF[INDEX + 1], namelen); wu->wu_param->pe_start = wu->wu_param->pe_end = wu->wu_param->pe_match = wu->wu_param->pe_subst = NULL; } INDEX += namelen + 1; return wu; } /* Parses a parameter expansion enclosed by { }. * The `ctxttype' parameter is the context type of the word containing this * parameter expansion. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. However, `pi->ctxt->pwords' is NULL * when the preceding words need to be determined by the caller. In this case, * the caller must update the `pwordc' and `pwords' member. */ wordunit_T *cparse_paramexp_in_brace(le_contexttype_T ctxttype) { paramexp_T *pe = xmalloc(sizeof *pe); pe->pe_type = 0; pe->pe_name = NULL; pe->pe_start = pe->pe_end = pe->pe_match = pe->pe_subst = NULL; const size_t origindex = INDEX; assert(BUF[INDEX] == L'{'); INDEX++; /* parse PT_NUMBER */ if (BUF[INDEX] == L'#') { switch (BUF[INDEX + 1]) { case L'}': case L'+': case L'=': case L':': case L'/': case L'%': break; case L'-': case L'?': case L'#': if (BUF[INDEX + 2] != L'}') break; /* falls thru! */ case L'\0': default: pe->pe_type |= PT_NUMBER; INDEX++; break; } } /* parse nested expansion */ if (BUF[INDEX] == L'{') { pe->pe_type |= PT_NEST; pe->pe_nest = cparse_paramexp_in_brace(ctxttype & CTXT_MASK); if (pe->pe_nest == NULL) goto return_null; } else if (BUF[INDEX] == L'`' || (BUF[INDEX] == L'$' && (BUF[INDEX + 1] == L'{' || BUF[INDEX + 1] == L'('))) { pe->pe_type |= PT_NEST; pe->pe_nest = cparse_special_word_unit(ctxttype & CTXT_MASK); if (pe->pe_nest == NULL) goto return_null; } else { /* no nesting: parse parameter name */ size_t namelen; switch (BUF[INDEX]) { case L'@': case L'*': case L'#': case L'?': case L'-': case L'$': case L'!': namelen = 1; break; default: namelen = 0; while (is_name_char(BUF[INDEX + namelen])) namelen++; break; } if (BUF[INDEX + namelen] == L'\0') { pi->ctxt->quote = QUOTE_NORMAL; pi->ctxt->type = CTXT_VAR | CTXT_VBRACED | (ctxttype & CTXT_QUOTED); pi->ctxt->pwordc = 0; pi->ctxt->pwords = xmalloc(1 * sizeof *pi->ctxt->pwords); pi->ctxt->pwords[0] = NULL; pi->ctxt->pattern = xwcsndup(&BUF[INDEX], namelen); pi->ctxt->srcindex = le_main_index - namelen; goto return_null; } pe->pe_name = xwcsndup(&BUF[INDEX], namelen); INDEX += namelen; } /* parse index */ if (BUF[INDEX] == L'[') { INDEX++; wordunit_T *w = cparse_word(is_closing_bracket, TT_NONE, CTXT_ARITH); if (w == NULL) goto return_null; wordfree(w); /* don't assign the result to `pe->pe_start/end' since it may cause an * arithmetic expansion error. */ if (BUF[INDEX] == L']') INDEX++; } /* parse PT_COLON */ if (BUF[INDEX] == L':') { pe->pe_type |= PT_COLON; INDEX++; } /* parse '-', '+', '#', etc. */ switch (BUF[INDEX]) { case L'+': pe->pe_type |= PT_PLUS; goto parse_subst; case L'-': case L'=': case L'?': pe->pe_type |= PT_MINUS; goto parse_subst; case L'#': pe->pe_type |= PT_MATCH | PT_MATCHHEAD; goto parse_match; case L'%': pe->pe_type |= PT_MATCH | PT_MATCHTAIL; goto parse_match; case L'/': pe->pe_type |= PT_SUBST | PT_MATCHLONGEST; goto parse_match; case L'}': pe->pe_type |= PT_NONE; goto check_closing_paren; default: goto syntax_error; } parse_match: if (pe->pe_type & PT_COLON) { if ((pe->pe_type & PT_MASK) == PT_SUBST) pe->pe_type |= PT_MATCHHEAD | PT_MATCHTAIL; else goto syntax_error; INDEX++; } else if (BUF[INDEX] == BUF[INDEX + 1]) { if ((pe->pe_type & PT_MASK) == PT_MATCH) pe->pe_type |= PT_MATCHLONGEST; else pe->pe_type |= PT_SUBSTALL; INDEX += 2; } else if (BUF[INDEX] == L'/') { switch (BUF[INDEX + 1]) { case L'#': pe->pe_type |= PT_MATCHHEAD; INDEX += 2; break; case L'%': pe->pe_type |= PT_MATCHTAIL; INDEX += 2; break; default: INDEX += 1; break; } } else { INDEX++; } if ((pe->pe_type & PT_MASK) == PT_MATCH) { pe->pe_match = cparse_word( is_closing_brace, TT_NONE, ctxttype | CTXT_VBRACED); if (pe->pe_match == NULL) goto return_null; goto check_closing_paren; } else { pe->pe_match = cparse_word( is_slash_or_closing_brace, TT_NONE, ctxttype | CTXT_VBRACED); if (pe->pe_match == NULL) goto return_null; if (BUF[INDEX] != L'/') goto check_closing_paren; } parse_subst: INDEX++; pe->pe_subst = cparse_word( is_closing_brace, TT_NONE, ctxttype | CTXT_VBRACED); if (pe->pe_subst == NULL) goto return_null; check_closing_paren: if (BUF[INDEX] == L'}') INDEX++; switch (pe->pe_type & PT_MASK) { case PT_MINUS: case PT_PLUS: break; case PT_ASSIGN: case PT_ERROR: assert(false); default: if (pe->pe_type & PT_NEST) break; /* make the variable expansion nested with the PT_MINUS flag * to avoid a possible "nounset" error. */ paramexp_T *pe2 = xmalloc(sizeof *pe2); pe2->pe_type = PT_MINUS; pe2->pe_name = pe->pe_name; pe2->pe_start = pe2->pe_end = pe2->pe_match = pe2->pe_subst = NULL; wordunit_T *nest = xmalloc(sizeof *nest); nest->next = NULL; nest->wu_type = WT_PARAM; nest->wu_param = pe2; pe->pe_type |= PT_NEST; pe->pe_nest = nest; break; } wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_PARAM; result->wu_param = pe; return result; syntax_error: paramfree(pe); result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_STRING; result->wu_string = escapefree( xwcsndup(&BUF[origindex], INDEX - origindex), NULL); return result; return_null: paramfree(pe); return NULL; } /* Parses an arithmetic expansion. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. */ wordunit_T *cparse_arith(void) { size_t startindex = INDEX; assert(BUF[INDEX ] == L'$'); assert(BUF[INDEX + 1] == L'('); unsigned nestparen = 0; INDEX += 2; for (;;) { if (BUF[INDEX] != L'\0' && is_arith_delimiter(BUF[INDEX])) { switch (BUF[INDEX++]) { case L'(': nestparen++; break; case L')': if (nestparen == 0) goto return_content; nestparen--; break; } } else { wordunit_T *w = cparse_word(is_arith_delimiter, TT_NONE, CTXT_ARITH); if (w == NULL) { empty_pwords(); return NULL; } wordfree(w); } } return_content:; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_STRING; result->wu_string = xwcsndup(&BUF[startindex], INDEX - startindex); return result; } /* Parses a command substitution enclosed by "$( )". * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. */ wordunit_T *cparse_cmdsubst_in_paren(void) { size_t startindex = INDEX; assert(BUF[INDEX ] == L'$'); assert(BUF[INDEX + 1] == L'('); INDEX += 2; if (cparse_commands()) { return NULL; } else { assert(BUF[INDEX] == L')'); INDEX++; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_STRING; result->wu_string = xwcsndup(&BUF[startindex], INDEX - startindex); return result; } } /* Parses a command substitution enclosed by backquotes. * If the parser reached the end of the input string, the return value is NULL * and the result is saved in `pi->ctxt'. */ wordunit_T *cparse_cmdsubst_in_backquote(void) { size_t startindex = INDEX; size_t endindex; assert(BUF[INDEX] == L'`'); INDEX++; endindex = INDEX; for (;;) { switch (BUF[endindex++]) { case L'\0': goto parse_inside; case L'`': goto return_content; case L'\\': if (BUF[endindex] != L'\0') endindex++; break; } } parse_inside: while (!cparse_commands()) INDEX++; return NULL; return_content: INDEX = endindex; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_STRING; result->wu_string = escapefree(xwcsndup(&BUF[startindex], endindex - startindex), NULL); return result; } bool is_slash_or_closing_brace(wchar_t c) { return c == L'/' || c == L'}' || c == L'\0'; } bool is_closing_brace(wchar_t c) { return c == L'}' || c == L'\0'; } bool is_closing_bracket(wchar_t c) { return c == L']' || c == L'\0'; } bool is_arith_delimiter(wchar_t c) { switch (c) { case L'\0': return true; case L'$': case L'`': case L'"': case L'\'': case L'\\': case L'_': return false; default: return !iswalnum(c); } } /* Remove brace expansion in the specified string and returns the last result of * expansion. * For example, remove_braceexpand("a{1,2{b,c}2{d,e") = "a2c2e". * The string is modified in place. * Returns true iff expansion is unclosed. */ bool remove_braceexpand(wchar_t *s) { bool unclosed = false; xwcsbuf_T buf; wb_init(&buf); wb_cat(&buf, s); for (size_t i = 0; i < buf.length; ) { switch (buf.contents[i]) { case L'{': unclosed = remove_braceexpand_inner(&buf, &i); break; case L'\\': i += 2; break; default: i++; break; } } assert(buf.length <= wcslen(s)); wmemcpy(s, buf.contents, buf.length + 1); wb_destroy(&buf); return unclosed; } bool remove_braceexpand_inner(xwcsbuf_T *buf, size_t *index) { bool foundcomma = false; size_t i = *index; size_t leftbraceindex = i; assert(buf->contents[leftbraceindex] == L'{'); i++; while (i < buf->length) { switch (buf->contents[i]) { case L'{': remove_braceexpand_inner(buf, &i); break; case L',': foundcomma = true; wb_remove(buf, leftbraceindex, i - leftbraceindex + 1); i = leftbraceindex; break; case L'}': if (foundcomma) /* remove right brace */ wb_remove(buf, i, 1); else i++; *index = i; return false; case L'\\': i += 2; break; default: i++; break; } } if (!foundcomma) /* remove left brace */ wb_remove(buf, leftbraceindex, 1); *index = buf->length; return true; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/compparse.h000066400000000000000000000017761354143602500163710ustar00rootroot00000000000000/* Yash: yet another shell */ /* compparse.h: simple parser for command line completion */ /* (C) 2007-2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_COMPPARSE_H #define YASH_COMPPARSE_H struct le_context_T; extern struct le_context_T *le_get_context(void) __attribute__((malloc,warn_unused_result)); #endif /* YASH_COMPPARSE_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/display.c000066400000000000000000001322511354143602500160310ustar00rootroot00000000000000/* Yash: yet another shell */ /* display.c: display control */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "display.h" #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include "../history.h" #include "../job.h" #include "../option.h" #include "../plist.h" #include "../strbuf.h" #include "../util.h" #include "complete.h" #include "editing.h" #include "terminfo.h" /* Characters displayed on the screen by line-editing are divided into three * parts: the prompt, the edit line and the candidate area. * The prompt is immediately followed by the edit line (on the same line) but * the candidate area are always on separate lines. * The three parts are printed in this order, from upper to lower. * When history search is active, the edit line is replaced by the temporary * search result line and the search target line. * * We have to track the cursor position each time we print a character on the * screen, so that we can correctly reposition the cursor later. To do this, we * use a buffer (`lebuf') accompanied by a position data. The `lebuf_putwchar' * function appends a wide character to the buffer and updates the position data * accordingly. * * In most terminals, when as many characters are printed as the number of * the columns, the cursor temporarily sticks to the end of the line. The cursor * moves to the next line immediately before the next character is printed. So * we must take care not to move the cursor (by the "cub1" capability, etc.) * when the cursor is sticking, or we cannot track the cursor position * correctly. To deal with this problem, if we finish printing a text at the end * of a line, we print a dummy space character and erase it to ensure the cursor * is no longer sticking. */ #if HAVE_WCWIDTH # ifndef wcwidth extern int wcwidth(wchar_t c); # endif #else # undef wcwidth # define wcwidth(c) (iswprint(c) ? 1 : 0) #endif /********** The Print Buffer **********/ static void lebuf_init_with_max(le_pos_T p, int maxcolumn); static void lebuf_wprintf(bool convert_cntrl, const wchar_t *format, ...) __attribute__((nonnull(2))); static const wchar_t *print_color_seq(const wchar_t *s) __attribute__((nonnull)); static bool lebuf_putchar1_trunc(int c); /* The print buffer. */ struct lebuf_T lebuf; /* Initializes the print buffer with the specified position data. */ void lebuf_init(le_pos_T p) { lebuf_init_with_max(p, le_columns); } /* Initializes the print buffer with the specified position data. * The number of columns in a line is specified by `maxcolumn'. * If `maxcolumn' is negative, it is considered as infinite. */ void lebuf_init_with_max(le_pos_T p, int maxcolumn) { lebuf.pos = p; lebuf.maxcolumn = maxcolumn; sb_init(&lebuf.buf); } /* Appends the specified byte to the print buffer without updating the position * data. Returns the appended character `c'. */ /* The signature of this function is so defined as to match that of the * `putchar' function. */ int lebuf_putchar(int c) { sb_ccat(&lebuf.buf, TO_CHAR(c)); return c; } /* Updates the position data of the print buffer as if a character with the * specified width has been printed. * The specified width must not be less than zero. */ void lebuf_update_position(int width) { assert(width >= 0); int new_column = lebuf.pos.column + width; if (lebuf.maxcolumn < 0 || new_column < lebuf.maxcolumn || (le_ti_xenl && new_column == lebuf.maxcolumn)) lebuf.pos.column = new_column; else if (new_column == lebuf.maxcolumn) lebuf.pos.line++, lebuf.pos.column = 0; else lebuf.pos.line++, lebuf.pos.column = width; } /* Appends the specified wide character to the print buffer without updating the * position data. */ void lebuf_putwchar_raw(wchar_t c) { sb_printf(&lebuf.buf, "%lc", (wint_t) c); } /* Appends the specified wide character to the print buffer and updates the * position data accordingly. * If `convert_cntrl' is true, a non-printable character is converted to the * '^'-prefixed form or the bracketed form. */ void lebuf_putwchar(wchar_t c, bool convert_cntrl) { int width = wcwidth(c); if (width > 0) { /* printable character */ lebuf_update_position(width); lebuf_putwchar_raw(c); } else { /* non-printable character */ if (!convert_cntrl) { switch (c) { case L'\a': lebuf_print_alert(false); return; case L'\n': lebuf_print_nel(); return; case L'\r': lebuf_print_cr(); return; default: lebuf_putwchar_raw(c); return; } } else { if (c < L'\040') { lebuf_putwchar(L'^', false); lebuf_putwchar(c + L'\100', true); } else if (c == L'\177') { lebuf_putwchar(L'^', false); lebuf_putwchar(L'?', false); } else { lebuf_wprintf(false, L"<%jX>", (uintmax_t) c); } } } } /* Appends the specified string to the print buffer. */ void lebuf_putws(const wchar_t *s, bool convert_cntrl) { for (size_t i = 0; s[i] != L'\0'; i++) lebuf_putwchar(s[i], convert_cntrl); } /* Appends the formatted string to the print buffer. */ void lebuf_wprintf(bool convert_cntrl, const wchar_t *format, ...) { va_list args; wchar_t *s; va_start(args, format); s = malloc_vwprintf(format, args); va_end(args); lebuf_putws(s, convert_cntrl); free(s); } /* Prints the specified prompt string to the print buffer. * Escape sequences, which are defined in "../input.c", are handled in this * function. */ void lebuf_print_prompt(const wchar_t *s) { le_pos_T save_pos = lebuf.pos; while (*s != L'\0') { if (*s != L'\\') { lebuf_putwchar(*s, false); } else switch (*++s) { default: lebuf_putwchar(*s, false); break; case L'\0': lebuf_putwchar(L'\\', false); goto done; // case L'\\': lebuf_putwchar(L'\\', false); break; case L'a': lebuf_putwchar(L'\a', false); break; case L'e': lebuf_putwchar(L'\033', false); break; case L'n': lebuf_putwchar(L'\n', false); break; case L'r': lebuf_putwchar(L'\r', false); break; case L'$': lebuf_putwchar(geteuid() != 0 ? L'$' : L'#', false); break; case L'j': lebuf_wprintf(false, L"%zu", job_count()); break; case L'!': lebuf_wprintf(false, L"%u", next_history_number()); break; case L'[': save_pos = lebuf.pos; break; case L']': lebuf.pos = save_pos; break; case L'f': s = print_color_seq(s); continue; } s++; } done:; } /* Prints a sequence to change the terminal font. * When this function is called, `*s' must be L'f' after the backslash. * This function returns a pointer to the character just after the escape * sequence. */ const wchar_t *print_color_seq(const wchar_t *s) { assert(s[-1] == L'\\'); assert(s[ 0] == L'f'); #define SETFG(color) \ lebuf_print_setfg(LE_COLOR_##color + (s[1] == L't' ? 8 : 0)) #define SETBG(color) \ lebuf_print_setbg(LE_COLOR_##color + (s[1] == L't' ? 8 : 0)) for (;;) switch (*++s) { case L'k': SETFG(BLACK); break; case L'r': SETFG(RED); break; case L'g': SETFG(GREEN); break; case L'y': SETFG(YELLOW); break; case L'b': SETFG(BLUE); break; case L'm': SETFG(MAGENTA); break; case L'c': SETFG(CYAN); break; case L'w': SETFG(WHITE); break; case L'K': SETBG(BLACK); break; case L'R': SETBG(RED); break; case L'G': SETBG(GREEN); break; case L'Y': SETBG(YELLOW); break; case L'B': SETBG(BLUE); break; case L'M': SETBG(MAGENTA); break; case L'C': SETBG(CYAN); break; case L'W': SETBG(WHITE); break; case L'd': lebuf_print_op(); break; case L'D': lebuf_print_sgr0(); break; case L's': lebuf_print_smso(); break; case L'u': lebuf_print_smul(); break; case L'v': lebuf_print_rev(); break; case L'n': lebuf_print_blink(); break; case L'i': lebuf_print_dim(); break; case L'o': lebuf_print_bold(); break; case L'x': lebuf_print_invis(); break; default: if (!iswalnum(*s)) goto done; } done: if (*s == L'.') s++; return s; #undef SETFG #undef SETBG } /* Like `lebuf_putwchar', but prints only if there are enough column to the * right of the current position. * Returns false iff there is not enough column. * Returns true for a non-printable character. */ bool lebuf_putwchar_trunc(wchar_t c) { int width = wcwidth(c); if (width <= 0) return true; int new_column = lebuf.pos.column + width; if (0 <= lebuf.maxcolumn && lebuf.maxcolumn <= new_column) return false; lebuf.pos.column = new_column; lebuf_putwchar_raw(c); return true; } /* Like `lebuf_putws', but stops printing when the cursor reaches the end of * the line. Non-printable characters are ignored. */ void lebuf_putws_trunc(const wchar_t *s) { while (*s != L'\0' && lebuf_putwchar_trunc(*s)) s++; } /* Like `lebuf_putwchar_trunc', but prints a byte instead of a wide character. * The width of the character byte is assumed to be 1. */ bool lebuf_putchar1_trunc(int c) { if (0 < lebuf.maxcolumn && lebuf.maxcolumn <= lebuf.pos.column + 1) return false; lebuf.pos.column += 1; sb_ccat(&lebuf.buf, TO_CHAR(c)); return true; } /********** Displaying **********/ typedef struct candpage_T candpage_T; typedef struct candcol_T candcol_T; static void finish(void); static void clear_to_end_of_screen(void); static void clear_editline(void); static void maybe_print_promptsp(void); static void update_editline(void); static bool current_display_is_uptodate(size_t index) __attribute__((pure)); static void check_cand_overwritten(void); static void update_styler(void); static void reset_style_before_moving(void); static void update_right_prompt(void); static void print_search(void); static void go_to(le_pos_T p); static void go_to_index(size_t index); static void go_to_after_editline(void); static void fillip_cursor(void); static void print_candidate_rawvalue(const le_candidate_T *cand) __attribute__((nonnull)); static void update_candidates(void); static void make_pages_and_columns(void); static bool arrange_candidates(size_t cand_per_col, int totalwidthlimit); static void divide_candidates_pages(size_t cand_per_col); static void free_candpage(void *candpage) __attribute__((nonnull)); static void free_candcol(void *candcol) __attribute__((nonnull)); static void print_candidates_all(void); static void update_highlighted_candidate(void); static void print_candidate(const le_candidate_T *cand, const candcol_T *col, bool highlight, bool printdesc) __attribute__((nonnull)); static void print_candidate_count(size_t pageindex); static void print_candidate_count_0(void); static size_t col_of_cand(size_t candindex); static int col_of_cand_cmp(const void *candindexp, const void *colp) __attribute__((nonnull)); static size_t page_of_col(size_t colindex); static int page_of_col_cmp(const void *colindexp, const void *pagep) __attribute__((nonnull)); static size_t select_list_item(size_t index, int offset, size_t listsize) __attribute__((const)); /* True when the prompt is displayed on the screen. */ /* The print buffer, `rprompt', and `sprompt' are valid iff `display_active' is * true. */ static bool display_active = false; /* The current cursor position. */ static le_pos_T current_position; /* The maximum number of lines that have been displayed. */ static int line_max; /* The set of strings printed as the prompt, right prompt, after prompt. * May contain escape sequences. */ static struct promptset_T prompt; /* The position of the first character of the edit line, just after the prompt. */ static le_pos_T editbasepos; /* The content of the edit line that is currently displayed on the screen. */ static wchar_t *current_editline = NULL; /* An array of cursor positions of each character in the edit line. * If the nth character of `current_editline' is positioned at line `l', column * `c', then cursor_positions[n] == l * le_columns + c. */ static int *cursor_positions = NULL; /* The current index in the edit line that divides the line into two (cf. * `le_main_buffer'). */ static size_t current_length = 0; /* The line number of the last edit line (or the search buffer). */ static int last_edit_line; /* True when the terminal's current font setting is the one set by the styler * prompt. */ static bool styler_active; /* The line on which the right prompt is displayed. * The value is -1 when the right prompt is not displayed. */ static int rprompt_line; /* The value of the right prompt where escape sequences have been processed. */ static struct { char *value; size_t length; /* number of bytes in `value' */ int width; /* width of right prompt on screen */ } rprompt; /* The value of the styler prompt where escape sequences have been processed. */ static struct { char *value; size_t length; /* number of bytes in `value' */ } sprompt; /* The type of completion candidate pages. */ struct candpage_T { size_t colindex; /* index of the first column in this page */ size_t colcount; /* number of the columns in this page */ }; /* The type of completion candidate columns. */ struct candcol_T { size_t candindex; /* index of the first candidate in this column */ size_t candcount; /* number of the candidates in this column */ int valuewidth; /* max width of the candidate values */ int descwidth; /* max width of the candidate descriptions */ int width; /* total width of the whole column. */ }; /* A list of completion candidate pages. * The elements pointed to by `candpages.contents[*]' are of type `candpage_T'. */ static plist_T candpages = { .contents = NULL }; /* A list of completion candidate columns. * The elements pointed to by `candcols.contents[*]' are of type `candcol_T'. * `candcols' is active iff `candpages' is active. */ static plist_T candcols = { .contents = NULL }; /* The index of the candidate that is currently highlighted. * An invalid index indicates that no candidate is highlighted. */ static size_t candhighlight; #define NOHIGHLIGHT ((size_t) -1) /* The number of the first line on which the candidate area is displayed. * When the candidate area is inactive, this value is negative. */ /* When the candidate area is overwritten by the edit line or the right prompt, * this value is updated to the first line number of the remaining area. */ static int candbaseline; /* When the candidate area is overwritten by the edit line or the right prompt, * this flag is set. */ static bool candoverwritten; /* Initializes the display module. */ void le_display_init(struct promptset_T prompt_) { prompt = prompt_; } /* Updates the prompt and the edit line, clears the candidate area, and leave * the cursor at a blank line. * Must be called before `le_editing_finalize'. */ void le_display_finalize(void) { assert(le_search_buffer.contents == NULL); le_display_update(false); lebuf_print_sgr0(); go_to_after_editline(); finish(); } /* Clears prompt, edit line and candidate area on the screen. * If `clear' is true, also prints the "clear" capability. */ void le_display_clear(bool clear) { if (display_active) { lebuf_init(current_position); lebuf_print_sgr0(); if (clear) lebuf_print_clear(); else go_to((le_pos_T) { 0, 0 }); finish(); } } /* Deactivates the display. * Can be called only when `display_active' is true. * This function clears the completion candidates, flushes the `lebuf' buffer, * and frees data that are no longer in use. */ void finish(void) { assert(display_active); clear_to_end_of_screen(), candbaseline = -1; le_display_complete_cleanup(); free(current_editline), current_editline = NULL; free(cursor_positions), cursor_positions = NULL; free(rprompt.value); free(sprompt.value); le_display_flush(); display_active = false; } /* Flushes the contents of the print buffer to the standard error and destroys * the buffer. */ void le_display_flush(void) { current_position = lebuf.pos; fwrite(lebuf.buf.contents, 1, lebuf.buf.length, stderr); fflush(stderr); sb_destroy(&lebuf.buf); } /* Clears the screen area below the cursor. * The cursor must be at the beginning of a line. */ void clear_to_end_of_screen(void) { assert(lebuf.pos.column == 0); reset_style_before_moving(); if (lebuf_print_ed()) /* if the terminal has "ed" capability, just use it */ return; int saveline = lebuf.pos.line; for (;;) { lebuf_print_el(); if (lebuf.pos.line >= line_max) break; lebuf_print_nel(); } go_to((le_pos_T) { saveline, 0 }); } /* Clears (part of) the edit line on the screen from the current cursor * position to the end of the edit line. * The prompt and the candidate area are not cleared. * When this function is called, the cursor must be positioned within the edit * line. When this function returns, the cursor is moved back to that position. */ void clear_editline(void) { assert(lebuf.pos.line > editbasepos.line || (lebuf.pos.line == editbasepos.line && lebuf.pos.column >= editbasepos.column)); le_pos_T save_pos = lebuf.pos; reset_style_before_moving(); for (;;) { lebuf_print_el(); if (lebuf.pos.line >= last_edit_line) break; lebuf_print_nel(); } rprompt_line = -1; go_to(save_pos); } /* (Re)prints the display appropriately and, if `cursor' is true, moves the * cursor to the proper position. * The print buffer must not have been initialized. * The output is sent to the print buffer. The buffer needs to be flushed for * its contents to be actually displayed. */ void le_display_update(bool cursor) { if (display_active) { lebuf_init(current_position); } else { display_active = true; last_edit_line = line_max = 0; candhighlight = NOHIGHLIGHT, candbaseline = -1, candoverwritten = false; /* prepare the right prompt */ lebuf_init((le_pos_T) { 0, 0 }); lebuf_print_sgr0(); lebuf_print_prompt(prompt.right); if (lebuf.pos.line != 0) { /* right prompt must be one line */ sb_clear(&lebuf.buf); /* lebuf.pos.line = */ lebuf.pos.column = 0; } rprompt.value = lebuf.buf.contents; rprompt.length = lebuf.buf.length; rprompt.width = lebuf.pos.column; rprompt_line = -1; /* prepare the styler prompt */ lebuf_init((le_pos_T) { 0, 0 }); lebuf_print_sgr0(); lebuf_print_prompt(prompt.styler); if (lebuf.pos.line != 0 || lebuf.pos.column != 0) { /* styler prompt must have no width */ sb_clear(&lebuf.buf); /* lebuf.pos.line = lebuf.pos.column = 0; */ } sprompt.value = lebuf.buf.contents; sprompt.length = lebuf.buf.length; styler_active = false; /* print main prompt */ lebuf_init((le_pos_T) { 0, 0 }); maybe_print_promptsp(); lebuf_print_prompt(prompt.main); fillip_cursor(); editbasepos = lebuf.pos; } if (le_search_buffer.contents == NULL) { /* print edit line */ update_editline(); } else { /* print search line */ print_search(); return; } /* print right prompt */ update_right_prompt(); /* print completion candidates */ update_candidates(); /* set cursor position */ assert(le_main_index <= le_main_buffer.length); if (cursor) { go_to_index(le_main_index); update_styler(); } } /* Prints a dummy string that moves the cursor to the first column of the next * line if the cursor is not at the first column. * This function does nothing if the "le-promptsp" option is not set. */ void maybe_print_promptsp(void) { if (shopt_le_promptsp) { lebuf_print_smso(); lebuf_putchar('$'); lebuf_print_sgr0(); for (int count = le_columns - (le_ti_xenl ? 1 : 2); --count >= 0; ) lebuf_putchar(' '); lebuf_print_cr(); lebuf_print_ed(); } } /* Prints the content of the edit line. * The cursor may be anywhere when this function is called. * The cursor is left at an unspecified position when this function returns. */ void update_editline(void) { size_t index = 0; if (current_editline != NULL) { /* We only reprint what have been changed from the last update: * skip the unchanged part at the beginning of the line. */ assert(cursor_positions != NULL); while (current_editline[index] != L'\0' && current_display_is_uptodate(index)) index++; /* return if nothing has changed */ if (current_editline[index] == L'\0' && le_main_buffer.contents[index] == L'\0') return; go_to_index(index); if (current_editline[index] != L'\0') clear_editline(); } else { /* print the whole edit line */ go_to(editbasepos); clear_editline(); } update_styler(); // No need to check for overflow in `le_main_buffer.length + 1' here. Should // overflow occur, the buffer would not have been allocated successfully. current_editline = xreallocn(current_editline, le_main_buffer.length + 1, sizeof *current_editline); cursor_positions = xreallocn(cursor_positions, le_main_buffer.length + 1, sizeof *cursor_positions); for (;;) { wchar_t c = le_main_buffer.contents[index]; current_editline[index] = c; cursor_positions[index] = lebuf.pos.line * lebuf.maxcolumn + lebuf.pos.column; if (index == le_main_buffer.length) break; if (styler_active && index >= le_main_length) lebuf_print_sgr0(), styler_active = false; lebuf_putwchar(c, true); index++; } current_length = le_main_length; fillip_cursor(); last_edit_line = (lebuf.pos.line >= rprompt_line) ? lebuf.pos.line : rprompt_line; /* clear the right prompt if the edit line reaches it. */ if (rprompt_line == lebuf.pos.line && lebuf.pos.column > lebuf.maxcolumn - rprompt.width - 2) { lebuf_print_el(); rprompt_line = -1; } else if (rprompt_line < lebuf.pos.line) { rprompt_line = -1; } /* clear the remaining of the current line if we're overwriting the * candidate area. */ check_cand_overwritten(); } bool current_display_is_uptodate(size_t index) { if (current_editline[index] != le_main_buffer.contents[index]) return false; if ((index == current_length || index == le_main_length) && current_length != le_main_length) return false; return true; } /* Sets the `candoverwritten' flag and clears to the end of line if the current * position is in the candidate area. */ void check_cand_overwritten(void) { if (0 <= candbaseline && candbaseline <= lebuf.pos.line) { lebuf_print_el(); candbaseline = lebuf.pos.line + 1; candoverwritten = true; } } /* If the styler prompt is inactive, prints it. */ void update_styler(void) { if (!styler_active) { sb_ncat_force(&lebuf.buf, sprompt.value, sprompt.length); styler_active = true; } } /* If the "msgr" capability is unavailable, prints the "sgr0" capability. */ /* Cursor-moving capabilities cannot be used in the standout mode unless the * "msgr" capability is available. We need to reset text style using the "sgr0" * capability before moving cursor. */ void reset_style_before_moving(void) { if (!le_ti_msgr) { lebuf_print_sgr0(); styler_active = false; } } /* Prints the right prompt if there is enough room in the edit line or if the * "le-alwaysrp" option is set. * The edit line must have been printed when this function is called. */ void update_right_prompt(void) { if (rprompt_line >= 0) return; if (rprompt.width == 0) return; if (lebuf.maxcolumn - rprompt.width - 2 < 0) return; int c = cursor_positions[le_main_buffer.length] % lebuf.maxcolumn; bool has_enough_room = (c <= lebuf.maxcolumn - rprompt.width - 2); if (!has_enough_room && !shopt_le_alwaysrp) return; go_to_index(le_main_buffer.length); if (!has_enough_room) { lebuf_print_nel(); check_cand_overwritten(); } lebuf_print_cuf(lebuf.maxcolumn - rprompt.width - lebuf.pos.column - 1); sb_ncat_force(&lebuf.buf, rprompt.value, rprompt.length); lebuf.pos.column += rprompt.width; last_edit_line = rprompt_line = lebuf.pos.line; styler_active = false; } /* Prints the current search result and the search line. * The cursor may be anywhere when this function is called. * Characters after the prompt are cleared in this function. * When this function returns, the cursor is left after the search line. */ void print_search(void) { assert(le_search_buffer.contents != NULL); free(current_editline), current_editline = NULL; free(cursor_positions), cursor_positions = NULL; go_to(editbasepos); clear_editline(); update_styler(); if (le_search_result != Histlist) lebuf_wprintf(true, L"%s", ashistentry(le_search_result)->value); reset_style_before_moving(); lebuf_print_nel(); clear_to_end_of_screen(), candbaseline = -1; update_styler(); switch (le_search_type) { const char *text; case SEARCH_PREFIX: assert(false); case SEARCH_VI: switch (le_search_direction) { case FORWARD: lebuf_putwchar(L'?', false); break; case BACKWARD: lebuf_putwchar(L'/', false); break; default: assert(false); } break; case SEARCH_EMACS: switch (le_search_direction) { case FORWARD: text = "Forward search: "; break; case BACKWARD: text = "Backward search: "; break; default: assert(false); } lebuf_wprintf(false, L"%s", gt(text)); break; } lebuf_putws(le_search_buffer.contents, true); fillip_cursor(); last_edit_line = lebuf.pos.line; } /* Moves the cursor to the specified position. * The target column must be less than `lebuf.maxcolumn'. */ void go_to(le_pos_T p) { if (line_max < lebuf.pos.line) line_max = lebuf.pos.line; assert(p.line <= line_max); assert(p.column < lebuf.maxcolumn); if (p.line == lebuf.pos.line) { if (lebuf.pos.column == p.column) return; reset_style_before_moving(); if (lebuf.pos.column < p.column) lebuf_print_cuf(p.column - lebuf.pos.column); else lebuf_print_cub(lebuf.pos.column - p.column); return; } reset_style_before_moving(); lebuf_print_cr(); if (lebuf.pos.line < p.line) lebuf_print_cud(p.line - lebuf.pos.line); else if (lebuf.pos.line > p.line) lebuf_print_cuu(lebuf.pos.line - p.line); if (p.column > 0) lebuf_print_cuf(p.column); } /* Moves the cursor to the character of the specified index in the main buffer. * This function relies on `cursor_positions', so `update_editline()' must have * been called beforehand. */ void go_to_index(size_t index) { int p = cursor_positions[index]; go_to((le_pos_T) { .line = p / lebuf.maxcolumn, .column = p % lebuf.maxcolumn }); } /* Moves the cursor to the beginning of the line below `last_edit_line'. * The edit line (and possibly the right prompt) must have been printed. */ void go_to_after_editline(void) { if (rprompt_line >= 0) { go_to((le_pos_T) { rprompt_line, lebuf.maxcolumn - 1 }); lebuf_print_nel(); } else { go_to_index(le_main_buffer.length); if (lebuf.pos.column != 0 || lebuf.pos.line == 0) lebuf_print_nel(); } } /* If the cursor is sticking to the end of line, moves it to the next line. */ void fillip_cursor(void) { if (0 <= lebuf.maxcolumn && lebuf.maxcolumn <= lebuf.pos.column) { lebuf_putwchar(L' ', false); lebuf_putwchar(L'\r', false); lebuf_print_el(); if (rprompt_line == lebuf.pos.line) rprompt_line = -1; } } /* Sets the `raw' and `width' members of candidates in `le_candidates'. * This function uses the print buffer to calculate the widths. */ void le_display_make_rawvalues(void) { assert(le_candidates.contents != NULL); for (size_t i = 0; i < le_candidates.length; i++) { le_candidate_T *cand = le_candidates.contents[i]; assert(cand->rawvalue.raw == NULL); lebuf_init_with_max((le_pos_T) { 0, 0 }, -1); print_candidate_rawvalue(cand); cand->rawvalue.raw = sb_tostr(&lebuf.buf); cand->rawvalue.width = lebuf.pos.column; assert(cand->rawdesc.raw == NULL); if (cand->desc != NULL) { lebuf_init_with_max((le_pos_T) { 0, 0 }, -1); lebuf_putws_trunc(cand->desc); cand->rawdesc.raw = sb_tostr(&lebuf.buf); cand->rawdesc.width = lebuf.pos.column; } } } /* Prints the "raw value" of the specified candidate to the print buffer. * The output is truncated when the cursor reaches the end of the line. */ void print_candidate_rawvalue(const le_candidate_T *cand) { const wchar_t *s = cand->value; if (cand->type == CT_FILE) { /* skip directory components for a file candidate */ for (;;) { const wchar_t *ss = wcschr(s, L'/'); if (ss == NULL || *++ss == L'\0') break; s = ss; } } else if (cand->type == CT_OPTION) { /* prepend a hyphen if none */ if (cand->value[0] != L'-') lebuf_putwchar_trunc(L'-'); } lebuf_putws_trunc(s); } /* Updates the candidate area. * The edit line (and the right prompt if any) must have been printed before * calling this function. * The cursor may be anywhere when this function is called. * The cursor is left at an unspecified position when this function returns. */ void update_candidates(void) { if (le_candidates.contents != NULL || candbaseline >= 0) { if (candoverwritten) le_display_complete_cleanup(); if (candpages.contents == NULL) { make_pages_and_columns(); print_candidates_all(); } else if (le_candidates.contents == NULL || candbaseline < 0 || candoverwritten) { print_candidates_all(); } else { update_highlighted_candidate(); } } } /* Arranges the current candidates in `le_candidates' to fit to the screen, * making pages and columns of candidates. * The edit line (and the right prompt if any) must have been printed before * calling this function. * The results are assigned to `candpages' and `candcols', which must not be * initialized when this function is called. * If there are too few lines or columns available to show the candidates on * the screen or if there are no candidates, this function does nothing. */ void make_pages_and_columns(void) { assert(candpages.contents == NULL); assert(candcols.contents == NULL); if (le_candidates.contents == NULL || le_candidates.length == 0) return; int maxrowi = le_lines - last_edit_line - 1; if (maxrowi < 2 || le_columns < 4) return; /* we need at least 2 lines & 4 columns */ pl_init(&candpages); pl_init(&candcols); #if INT_MAX > SIZE_MAX size_t maxrow = (maxrowi > SIZE_MAX) ? SIZE_MAX : (size_t) maxrowi; #else size_t maxrow = (size_t) maxrowi; #endif /* first check if the candidates fit into one page */ for (size_t cand_per_col = 1; cand_per_col <= maxrow; cand_per_col++) { if (arrange_candidates(cand_per_col, le_columns)) { candpage_T *page = xmalloc(sizeof *page); page->colindex = 0; page->colcount = candcols.length; pl_add(&candpages, page); return; } } /* divide the candidate list into pages */ divide_candidates_pages(maxrow - 1); } /* Tries to arrange the current candidates in `le_candidates' into columns that * have `cand_per_col' candidates each. * `cand_per_col' must be positive. * Candidate list `le_candidates' must not be empty. * If `totalwidthlimit' is non-negative and if the total width of the resulting * columns is >= `totalwidthlimit', then this function fails. * On success, the resulting columns are added to `candcols', which must have * been initialized as an empty list before calling this function, and true is * returned. On failure, `candcols' is not modified and false is returned. * This function never modifies `candpages'. */ bool arrange_candidates(size_t cand_per_col, int totalwidthlimit) { int totalwidth = 0; size_t candindex = 0; assert(cand_per_col > 0); assert(candcols.contents[0] == NULL); do { candcol_T *col = xmalloc(sizeof *col); col->candindex = candindex; if (le_candidates.length - candindex < cand_per_col) col->candcount = le_candidates.length - candindex; else col->candcount = cand_per_col; col->valuewidth = col->descwidth = col->width = 0; for (size_t nextcandindex = candindex + col->candcount; candindex < nextcandindex; candindex++) { le_candidate_T *cand = le_candidates.contents[candindex]; if (col->valuewidth < cand->rawvalue.width) col->valuewidth = cand->rawvalue.width; if (col->descwidth < cand->rawdesc.width) col->descwidth = cand->rawdesc.width; } col->valuewidth += 2; if (col->descwidth > 0) col->descwidth += 4; col->width = col->valuewidth + col->descwidth; totalwidth += col->width; pl_add(&candcols, col); if (totalwidthlimit >= 0 && totalwidth >= totalwidthlimit) { pl_clear(&candcols, free_candcol); return false; } } while (candindex < le_candidates.length); return true; } /* Divides the current candidates in `le_candidates' into columns that have * `cand_per_col' candidates each and divides the columns into pages that each * fit into the screen. * Candidate list `le_candidates' must not be empty. * The resulting columns and pages are added to `candcols' and `candpages', * which must have been initialized as empty lists before calling this function. */ void divide_candidates_pages(size_t cand_per_col) { /* divide candidates into columns */ bool ok = arrange_candidates(cand_per_col, -1); assert(ok); /* divide columns into pages */ size_t colindex = 0; assert(candpages.contents[0] == NULL); do { candpage_T *page = xmalloc(sizeof *page); int pagewidth; candcol_T *col; page->colindex = colindex; col = candcols.contents[colindex]; pagewidth = col->width; while ((col = candcols.contents[++colindex]) != NULL && pagewidth + col->width < le_columns) pagewidth += col->width; page->colcount = colindex - page->colindex; /* Each page contains at least one column: The page width may not * necessarily be less than the screen width. */ pl_add(&candpages, page); } while (colindex < candcols.length); /* col != NULL */ } /* Clears data used for displaying the candidate area. */ /* Must be called when the current candidates are updated. */ void le_display_complete_cleanup(void) { if (candpages.contents != NULL) { plfree(pl_toary(&candpages), free_candpage); candpages.contents = NULL; assert(candcols.contents != NULL); plfree(pl_toary(&candcols), free_candcol); candcols.contents = NULL; } candhighlight = NOHIGHLIGHT; } /* Frees a completion candidate page. * The argument must point to a `candpage_T' value. */ void free_candpage(void *candpage) { candpage_T *p = candpage; free(p); } /* Frees a completion candidate column. * The argument must point to a `candcol_T' value. */ void free_candcol(void *candcol) { candcol_T *c = candcol; free(c); } /* Prints the whole candidate area. * The edit line (and the right prompt, if any) must have been printed and * `make_pages_and_columns' must have been called before calling this function. * The cursor may be anywhere when this function is called. * The cursor is left at an unspecified position when this function returns. * If the candidate list is inactive, this function does nothing but clearing * the candidate area. If there are no candidates, an error message is printed. */ void print_candidates_all(void) { lebuf_print_sgr0(), styler_active = false; go_to_after_editline(); clear_to_end_of_screen(); assert(lebuf.pos.column == 0); candoverwritten = false; if (le_candidates.contents == NULL) return; if (le_candidates.length == 0) { candbaseline = lebuf.pos.line; print_candidate_count_0(); return; } if (candpages.contents == NULL) return; candbaseline = lebuf.pos.line; size_t pageindex = le_selected_candidate_index < le_candidates.length ? page_of_col(col_of_cand(le_selected_candidate_index)) : 0; const candpage_T *page = candpages.contents[pageindex]; const candcol_T *firstcol = candcols.contents[page->colindex]; for (size_t rowi = 0, rowcount = firstcol->candcount; ; ) { int scrcol = 0; for (size_t coli = 0; coli < page->colcount; coli++) { const candcol_T *col = candcols.contents[page->colindex + coli]; if (rowi >= col->candcount) break; while (lebuf.pos.column < scrcol) lebuf_putchar1_trunc(' '); size_t candindex = col->candindex + rowi; print_candidate(le_candidates.contents[candindex], col, le_selected_candidate_index == candindex, true); scrcol += col->width; } rowi++; if (rowi >= rowcount) break; lebuf_print_nel(); } if (candpages.length > 1) { /* print status line */ lebuf_print_nel(); print_candidate_count(pageindex); } candhighlight = le_selected_candidate_index; } /* Reprints the highlighted candidate. * The previously highlighted candidate is reprinted in the normal style and * the currently selected candidate is highlighted. * Before calling this function, the candidate area must have been printed by * `print_candidates_all'. * The cursor may be anywhere when this function is called and is left at an * unspecified position when this function returns. */ void update_highlighted_candidate(void) { assert(candbaseline >= 0); if (le_candidates.length == 0) return; if (candhighlight == le_selected_candidate_index) return; size_t oldpageindex, oldcolindex, newpageindex, newcolindex; if (candhighlight < le_candidates.length) { oldcolindex = col_of_cand(candhighlight); oldpageindex = page_of_col(oldcolindex); } else { oldcolindex = 0; oldpageindex = 0; } if (le_selected_candidate_index < le_candidates.length) { newcolindex = col_of_cand(le_selected_candidate_index); newpageindex = page_of_col(newcolindex); } else { newcolindex = 0; newpageindex = 0; } if (oldpageindex != newpageindex) { print_candidates_all(); return; } lebuf_print_sgr0(), styler_active = false; const candpage_T *page = candpages.contents[newpageindex]; const candcol_T *col; int column; size_t rowindex; /* de-highlight the previous selected candidate */ if (candhighlight < le_candidates.length) { column = 0; for (size_t i = page->colindex; i < oldcolindex; i++) { col = candcols.contents[i]; column += col->width; } col = candcols.contents[oldcolindex]; rowindex = candhighlight - col->candindex; go_to((le_pos_T) { .line = candbaseline + (int) rowindex, .column = column }); print_candidate( le_candidates.contents[candhighlight], col, false, false); } /* highlight the current selected candidate */ candhighlight = le_selected_candidate_index; if (candhighlight < le_candidates.length) { column = 0; for (size_t i = page->colindex; i < newcolindex; i++) { col = candcols.contents[i]; column += col->width; } col = candcols.contents[newcolindex]; rowindex = candhighlight - col->candindex; go_to((le_pos_T) { .line = candbaseline + (int) rowindex, .column = column }); print_candidate( le_candidates.contents[candhighlight], col, true, false); } /* print status line */ if (candpages.length > 1) { col = candcols.contents[page->colindex]; go_to((le_pos_T) { .line = candbaseline + col->candcount, .column = 0 }); lebuf_print_el(); print_candidate_count(newpageindex); } } /* Prints the specified candidate at the current cursor position. * The candidate is highlighted iff `highlight' is true. * Iff `printdesc' is true, the candidate's description is printed. * The cursor is left just after the printed candidate. */ void print_candidate(const le_candidate_T *cand, const candcol_T *col, bool highlight, bool printdesc) { int line = lebuf.pos.line; /* print value */ if (true /* cand->value != NULL */) { int base = lebuf.pos.column; if (highlight) lebuf_print_bold(); lebuf_putchar1_trunc(highlight ? '[' : ' '); if (lebuf.pos.column + cand->rawvalue.width < lebuf.maxcolumn) { lebuf.pos.column += cand->rawvalue.width; sb_cat(&lebuf.buf, cand->rawvalue.raw); } else { print_candidate_rawvalue(cand); } while (lebuf.pos.column + 2 < lebuf.maxcolumn && lebuf.pos.column - base < col->valuewidth - 1) lebuf_putchar1_trunc(' '); if (highlight) lebuf_print_sgr0(), lebuf_print_bold(); lebuf_putchar1_trunc(highlight ? ']' : ' '); if (highlight) lebuf_print_sgr0(); } /* print description */ if (printdesc && cand->desc != NULL) { lebuf_putchar1_trunc(' '); lebuf_putchar1_trunc('('); if (lebuf.pos.column + cand->rawdesc.width < lebuf.maxcolumn) { lebuf.pos.column += cand->rawdesc.width; sb_cat(&lebuf.buf, cand->rawdesc.raw); } else { lebuf_putws_trunc(cand->desc); } lebuf_putchar1_trunc(')'); } assert(lebuf.pos.line == line); } /* Prints the number of the currently selected candidate, the total number of * the candidates, the number of the current page, and the total number of the * pages at the current position. * The index of the page to which the selected candidate belong must be given * as `pageindex'. */ void print_candidate_count(size_t pageindex) { size_t sindex = (le_selected_candidate_index < le_candidates.length) ? le_selected_candidate_index + 1 : 0; char *s1 = malloc_printf(gt("Candidate %zu of %zu; Page %zu of %zu"), sindex, le_candidates.length, pageindex + 1, candpages.length); if (s1 != NULL) { wchar_t *s2 = realloc_mbstowcs(s1); if (s2 != NULL) { lebuf_putws_trunc(s2); free(s2); } } } /* Prints an error message that tells there are no candidates. */ void print_candidate_count_0(void) { wchar_t *s = malloc_mbstowcs(gt("No candidates")); if (s != NULL) { lebuf_putws_trunc(s); free(s); } } /* Returns the index of the column to which the candidate of index `candindex' * belongs. Column list `candcols' must not be empty. */ size_t col_of_cand(size_t candindex) { assert(candcols.length > 0); void **colp = bsearch(&candindex, candcols.contents, candcols.length, sizeof *candcols.contents, col_of_cand_cmp); assert(colp != NULL); return colp - candcols.contents; } int col_of_cand_cmp(const void *candindexp, const void *colp) { size_t candindex = *(const size_t *) candindexp; const candcol_T *col = *(void **) colp; if (candindex < col->candindex) return -1; else if (candindex < col->candindex + col->candcount) return 0; else return 1; } /* Returns the index of the page to which the column of index `colindex' * belongs. Page list `candpages' must not be empty. */ size_t page_of_col(size_t colindex) { assert(candpages.length > 0); void **pagep = bsearch(&colindex, candpages.contents, candpages.length, sizeof *candpages.contents, page_of_col_cmp); assert(pagep != NULL); return pagep - candpages.contents; } int page_of_col_cmp(const void *colindexp, const void *pagep) { size_t colindex = *(const size_t *) colindexp; const candpage_T *page = *(void **) pagep; if (colindex < page->colindex) return -1; else if (colindex < page->colindex + page->colcount) return 0; else return 1; } /* Sets `le_selected_candidate_index' to the index of the first candidate of * the `offset'th next column, counted from the column containing the currently * selected candidate. * If `candcols' is empty, `le_selected_candidate_index' is left unchanged. */ void le_display_select_column(int offset) { if (candcols.contents == NULL) return; size_t colindex = le_selected_candidate_index < le_candidates.length ? col_of_cand(le_selected_candidate_index) : 0; colindex = select_list_item(colindex, offset, candcols.length); const candcol_T *col = candcols.contents[colindex]; le_selected_candidate_index = col->candindex; } /* Sets `le_selected_candidate_index' to the index of the first candidate of * the `offset'th next page, counted from the page containing the currently * selected candidate. * If `candpages' is empty, `le_selected_candidate_index' is left unchanged. */ void le_display_select_page(int offset) { if (candpages.contents == NULL) return; size_t pageindex = le_selected_candidate_index < le_candidates.length ? page_of_col(col_of_cand(le_selected_candidate_index)) : 0; pageindex = select_list_item(pageindex, offset, candpages.length); const candpage_T *page = candpages.contents[pageindex]; const candcol_T *col = candcols.contents[page->colindex]; le_selected_candidate_index = col->candindex; } /* Computes `(index + offset) mod listsize'. */ size_t select_list_item(size_t index, int offset, size_t listsize) { if (offset >= 0) { offset %= listsize; index += offset; index %= listsize; } else { offset = -offset % listsize; if ((size_t) offset <= index) index -= offset; else index += listsize - offset; } assert(index < listsize); return index; } /********** Utility **********/ /* If the standard error is a terminal, prints the specified prompt to the * terminal and returns true. */ bool le_try_print_prompt(const wchar_t *s) { if (isatty(STDERR_FILENO) && le_setupterm(true)) { lebuf_init((le_pos_T) { 0, 0 }); lebuf_print_prompt(s); le_display_flush(); return true; } else { return false; } } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/display.h000066400000000000000000000041031354143602500160300ustar00rootroot00000000000000/* Yash: yet another shell */ /* display.h: display control */ /* (C) 2007-2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_DISPLAY_H #define YASH_DISPLAY_H #include #include "../input.h" #include "../strbuf.h" #include "lineedit.h" typedef struct le_pos_T { int line, column; } le_pos_T; extern struct lebuf_T { xstrbuf_T buf; le_pos_T pos; int maxcolumn; } lebuf; extern void lebuf_init(le_pos_T p); extern int lebuf_putchar(int c); extern void lebuf_update_position(int width); extern void lebuf_putwchar_raw(wchar_t c); extern void lebuf_putwchar(wchar_t c, _Bool convert_cntrl); extern void lebuf_putws(const wchar_t *s, _Bool convert_cntrl) __attribute__((nonnull)); extern void lebuf_print_prompt(const wchar_t *s) __attribute__((nonnull)); extern _Bool lebuf_putwchar_trunc(wchar_t c); extern void lebuf_putws_trunc(const wchar_t *s) __attribute__((nonnull)); extern void le_display_init(struct promptset_T prompt); extern void le_display_finalize(void); extern void le_display_clear(_Bool clear); extern void le_display_flush(void); extern void le_display_update(_Bool cursor); extern void le_display_make_rawvalues(void); extern void le_display_complete_cleanup(void); extern void le_display_select_column(int offset); extern void le_display_select_page(int offset); extern _Bool le_try_print_prompt(const wchar_t *s) __attribute__((nonnull)); #endif /* YASH_DISPLAY_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/editing.c000066400000000000000000003351671354143602500160220ustar00rootroot00000000000000/* Yash: yet another shell */ /* editing.c: main editing module */ /* (C) 2007-2017 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "editing.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "../alias.h" #include "../exec.h" #include "../expand.h" #include "../history.h" #include "../job.h" #include "../option.h" #include "../path.h" #include "../plist.h" #include "../redir.h" #include "../strbuf.h" #include "../util.h" #include "../xfnmatch.h" #include "../yash.h" #include "complete.h" #include "display.h" #include "keymap.h" #include "lineedit.h" #include "terminfo.h" #include "trie.h" /* The type of pairs of a command and an argument. */ struct le_command_T { le_command_func_T *func; wchar_t arg; }; /* The main buffer where the command line is edited. * The contents of the buffer is divided into two parts: The first part is the * main command line text that is input and edited by the user. The second is * automatically appended after the first as a result of the prediction * feature. As the user edits the first part, the prediction feature updates * the second. When the user moves the cursor to somewhere in the second part, * the text up to the cursor then becomes the first. */ xwcsbuf_T le_main_buffer; /* The position that divides the main buffer into two parts as described just * above. If `le_main_length > le_main_buffer.length', the second part is * assumed empty. */ size_t le_main_length; /* The position of the cursor on the command line. * le_main_index <= le_main_buffer.length */ size_t le_main_index; /* The history entry that is being edited in the main buffer now. * When we're editing no history entry, `main_history_entry' is `Histlist'. */ static const histlink_T *main_history_entry; /* The original value of `main_history_entry', converted into a wide string. */ static wchar_t *main_history_value; /* The direction of currently performed command history search. */ enum le_search_direction_T le_search_direction; /* The type of currently performed command history search. */ enum le_search_type_T le_search_type; /* Supplementary buffer used in command history search. * When search is not being performed, `le_search_buffer.contents' is NULL. */ xwcsbuf_T le_search_buffer; /* The search result for the current value of `le_search_buffer'. * If there is no match, `le_search_result' is `Histlist'. */ const histlink_T *le_search_result; /* The search string and the direction of the last search. */ static struct { enum le_search_direction_T direction; enum le_search_type_T type; wchar_t *value; } last_search; /* The last executed command and the currently executing command. */ static struct le_command_T last_command, current_command; /* The type of motion expecting commands. */ enum motion_expect_command_T { MEC_UPPERCASE = 1 << 0, /* convert the text to upper case */ MEC_LOWERCASE = 1 << 1, /* convert the text to lower case */ MEC_SWITCHCASE = MEC_UPPERCASE | MEC_LOWERCASE, /* switch case of text */ MEC_CASEMASK = MEC_SWITCHCASE, MEC_TOSTART = 1 << 2, /* move cursor to the beginning of the region */ MEC_TOEND = 1 << 3, /* move cursor to the end of the region */ MEC_MOVE = MEC_TOSTART | MEC_TOEND, /* move cursor to motion end */ MEC_CURSORMASK = MEC_MOVE, /* If none of MEC_TOSTART, MEC_TOEND, and MEC_MOVE is specified, the cursor * is not moved unless MEC_DELETE is specified. */ MEC_COPY = 1 << 4, /* copy the text to the kill ring */ MEC_DELETE = 1 << 5, /* delete the text */ MEC_INSERT = 1 << 6, /* go to insert mode */ MEC_KILL = MEC_COPY | MEC_DELETE, MEC_CHANGE = MEC_DELETE | MEC_INSERT, MEC_COPYCHANGE = MEC_KILL | MEC_INSERT, }; /* The state in which a command is executed. */ struct state_T { struct { /* When count is not specified, `sign' and `abs' are 0. * Otherwise, `sign' is 1 or -1. * When the negative sign is specified but digits are not, `abs' is 0.*/ int sign; unsigned abs; int multiplier; } count; enum motion_expect_command_T pending_command_motion; le_command_func_T *pending_command_char; }; #define COUNT_ABS_MAX 999999999 /* The current state. */ static struct state_T state; /* The last executed editing command and the then state. * Valid iff `.command.func' is non-null. */ static struct { struct le_command_T command; struct state_T state; } last_edit_command; /* The last executed find/till command. */ /* `last_find_command' is valid iff `.func' is non-null. */ static struct le_command_T last_find_command; /* The editing mode before the mode is changed to LE_MODE_CHAR_EXPECT/SEARCH. * When the char-expecting/search command finishes, the mode is restored to * this mode. */ static le_mode_id_T savemode; /* When starting the overwrite mode, the then `le_main_buffer' contents and * `le_main_length' are saved in this structure. The values are kept so that the * original contents can be restored when the user hits backspace. When the user * leaves the overwrite mode, `contents' is freed and set to NULL. */ static struct { wchar_t *contents; size_t length; } overwrite_save_buffer; /* History of the edit line between editing commands. */ static plist_T undo_history; /* Index of the current state in the history. * If the current state is the newest, the index is `undo_history.length'. */ static size_t undo_index; /* The history entry that is saved in the undo history. */ static const histlink_T *undo_history_entry; /* The index that is to be the value of the `index' member of the next undo * history entry. */ static size_t undo_save_index; /* Structure of history entries */ struct undo_history { size_t index; /* index of the cursor */ wchar_t contents[]; /* contents of the edit line */ // `contents' is a copy of `le_main_buffer.contents' up to `le_main_length'. }; #define KILL_RING_SIZE 32 /* must be power of 2 */ /* The kill ring */ static wchar_t *kill_ring[KILL_RING_SIZE]; /* The index of the element to which next killed string is assigned. */ static size_t next_kill_index = 0; /* The index of the last put element. */ static size_t last_put_elem = 0; /* < KILL_RING_SIZE */ /* The position and length of the last put string. */ static size_t last_put_range_start, last_put_range_length; /* Set to true if the next completion command should restart completion from * scratch. */ static bool reset_completion; /* The next value of `reset_completion'. */ static bool next_reset_completion; /* Probability distribution tree for command prediction. */ static trie_T *prediction_tree = NULL; static void reset_state(void); static void reset_count(void); static int get_count(int default_value) __attribute__((pure)); static size_t active_length(void) __attribute__((pure)); static void save_current_edit_command(void); static void save_current_find_command(void); static void save_undo_history(void); static void maybe_save_undo_history(void); static void exec_motion_command(size_t new_index, bool inclusive); static void set_motion_expect_command(enum motion_expect_command_T cmd); static void exec_motion_expect_command( enum motion_expect_command_T cmd, le_command_func_T motion); static void exec_motion_expect_command_line(enum motion_expect_command_T cmd); static void exec_motion_expect_command_all(void); static void add_to_kill_ring(const wchar_t *s, size_t n) __attribute__((nonnull)); static void set_char_expect_command(le_command_func_T cmd) __attribute__((nonnull)); static void set_overwriting(bool overwriting); static inline bool is_overwriting(void) __attribute__((pure)); static void restore_overwritten_buffer_contents( size_t start_index, size_t end_index); static void set_search_mode(le_mode_id_T mode, enum le_search_direction_T dir); static void to_upper_case(wchar_t *s, size_t n) __attribute__((nonnull)); static void to_lower_case(wchar_t *s, size_t n) __attribute__((nonnull)); static void switch_case(wchar_t *s, size_t n) __attribute__((nonnull)); static void set_mode(le_mode_id_T newmode, bool overwrite); static void redraw_all(bool clear); static bool alert_if_first(void); static bool alert_if_last(void); static void move_cursor_forward_char(int offset); static void move_cursor_backward_char(int offset); static void move_cursor_forward_bigword(int count); static void move_cursor_backward_bigword(int count); static size_t next_bigword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static size_t next_end_of_bigword_index( const wchar_t *s, size_t i, bool progress) __attribute__((nonnull)); static size_t previous_bigword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static void move_cursor_forward_semiword(int count); static void move_cursor_backward_semiword(int count); static size_t next_semiword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static size_t next_end_of_semiword_index( const wchar_t *s, size_t i, bool progress) __attribute__((nonnull)); static size_t previous_semiword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static void move_cursor_forward_viword(int count); static inline bool need_cw_treatment(void) __attribute__((pure)); static void move_cursor_backward_viword(int count); static size_t next_viword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static size_t next_end_of_viword_index( const wchar_t *s, size_t i, bool progress) __attribute__((nonnull)); static size_t previous_viword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static void move_cursor_forward_emacsword(int count); static void move_cursor_backward_emacsword(int count); static size_t next_emacsword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static size_t previous_emacsword_index(const wchar_t *s, size_t i) __attribute__((nonnull)); static void find_char(wchar_t c); static void find_char_rev(wchar_t c); static void till_char(wchar_t c); static void till_char_rev(wchar_t c); static void exec_find(wchar_t c, int count, bool till); static size_t find_nth_occurence(wchar_t c, int n); static void put_killed_string(bool after_cursor, bool cursor_on_last_char); static void insert_killed_string( bool after_cursor, bool cursor_on_last_char, size_t index); static void cancel_undo(int offset); static void check_reset_completion(void); static void create_prediction_tree(void); static size_t count_matching_previous_commands(const histentry_T *e1) __attribute__((nonnull,pure)); static void clear_prediction(void); static void update_buffer_with_prediction(void); static void vi_replace_char(wchar_t c); static void vi_exec_alias(wchar_t c); struct xwcsrange { const wchar_t *start, *end; }; static struct xwcsrange get_next_bigword(const wchar_t *s) __attribute__((nonnull)); static struct xwcsrange get_prev_bigword( const wchar_t *beginning, const wchar_t *s) __attribute__((nonnull)); static void replace_horizontal_space(bool deleteafter, const wchar_t *s) __attribute__((nonnull)); static void go_to_history_absolute( const histlink_T *l, enum le_search_type_T curpos) __attribute__((nonnull)); static void go_to_history_relative(int offset, enum le_search_type_T curpos); static void go_to_history(const histlink_T *l, enum le_search_type_T curpos) __attribute__((nonnull)); static bool need_update_last_search_value(void) __attribute__((pure)); static void update_search(void); static void perform_search(const wchar_t *pattern, enum le_search_direction_T dir, enum le_search_type_T type) __attribute__((nonnull)); static void search_again(enum le_search_direction_T dir); static void beginning_search(enum le_search_direction_T dir); static inline bool beginning_search_check_go_to_history(const wchar_t *prefix) __attribute__((nonnull,pure)); #define ALERT_AND_RETURN_IF_PENDING \ do if (state.pending_command_motion != MEC_MOVE) \ { cmd_alert(L'\0'); return; } \ while (0) /* Initializes the editing module before starting editing. */ void le_editing_init(void) { wb_init(&le_main_buffer); le_main_length = le_main_index = 0; main_history_entry = Histlist; main_history_value = xwcsdup(L""); switch (shopt_lineedit) { case SHOPT_VI: le_set_mode(LE_MODE_VI_INSERT); break; case SHOPT_EMACS: le_set_mode(LE_MODE_EMACS); break; default: assert(false); } last_command.func = 0; last_command.arg = L'\0'; start_using_history(); pl_init(&undo_history); undo_index = 0; undo_save_index = le_main_index; undo_history_entry = Histlist; save_undo_history(); reset_completion = true; reset_state(); set_overwriting(false); if (shopt_le_predict) { create_prediction_tree(); update_buffer_with_prediction(); } } /* Finalizes the editing module when editing is finished. * Returns the content of the main buffer, which must be freed by the caller. */ wchar_t *le_editing_finalize(void) { assert(le_search_buffer.contents == NULL); plfree(pl_toary(&undo_history), free); le_complete_cleanup(); end_using_history(); free(main_history_value); clear_prediction(); trie_destroy(prediction_tree), prediction_tree = NULL; wb_wccat(&le_main_buffer, L'\n'); return wb_towcs(&le_main_buffer); } /* Invokes the specified command. */ void le_invoke_command(le_command_func_T *cmd, wchar_t arg) { current_command.func = cmd; current_command.arg = arg; next_reset_completion = true; cmd(arg); last_command = current_command; reset_completion |= next_reset_completion; if (le_main_length < le_main_index) le_main_length = le_main_index; switch (le_editstate) { case LE_EDITSTATE_EDITING: if (shopt_le_predict) update_buffer_with_prediction(); break; case LE_EDITSTATE_DONE: case LE_EDITSTATE_ERROR: clear_prediction(); break; case LE_EDITSTATE_INTERRUPTED: break; } if (LE_CURRENT_MODE == LE_MODE_VI_COMMAND) if (le_main_index > 0 && le_main_index == le_main_buffer.length) le_main_index--; } /* Resets `state'. */ void reset_state(void) { reset_count(); state.pending_command_motion = MEC_MOVE; state.pending_command_char = 0; } /* Resets `state.count'. */ void reset_count(void) { state.count.sign = 0; state.count.abs = 0; state.count.multiplier = 1; } /* Returns the count value. * If the count is not set, returns the `default_value'. */ int get_count(int default_value) { long long result; if (state.count.sign == 0) result = (long long) default_value * state.count.multiplier; else if (state.count.sign < 0 && state.count.abs == 0) result = (long long) -state.count.multiplier; else result = (long long) state.count.abs * state.count.sign * state.count.multiplier; if (result < -COUNT_ABS_MAX) result = -COUNT_ABS_MAX; else if (result > COUNT_ABS_MAX) result = COUNT_ABS_MAX; return result; } /* Returns the length of the first part of `le_main_buffer'. */ size_t active_length(void) { if (le_main_length > le_main_buffer.length) return le_main_buffer.length; if (le_main_length < le_main_index) return le_main_index; return le_main_length; } /* Saves the currently executing command and the current state in * `last_edit_command' if we are not redoing and the mode is not "vi insert". */ void save_current_edit_command(void) { if (current_command.func != cmd_redo && LE_CURRENT_MODE != LE_MODE_VI_INSERT) { last_edit_command.command = current_command; last_edit_command.state = state; } } /* Saves the currently executing command and the current state in * `last_find_command' if we are not redoing/refinding. */ void save_current_find_command(void) { if (current_command.func != cmd_refind_char && current_command.func != cmd_refind_char_rev && current_command.func != cmd_redo) last_find_command = current_command; } /* Saves the current contents of the edit line to the undo history. * History entries at the current `undo_index' and newer are removed before * saving the current. If `undo_history_entry' is different from * `main_history_entry', all undo history entries are removed. */ void save_undo_history(void) { for (size_t i = undo_index; i < undo_history.length; i++) free(undo_history.contents[i]); pl_remove(&undo_history, undo_index, SIZE_MAX); // No need to check for overflow in `len + 1' here. Should overflow occur, // the buffer would not have been allocated successfully. size_t len = active_length(); struct undo_history *e = xmallocs(sizeof *e, len + 1, sizeof *e->contents); e->index = le_main_index; wcsncpy(e->contents, le_main_buffer.contents, len); e->contents[len] = L'\0'; pl_add(&undo_history, e); assert(undo_index == undo_history.length - 1); undo_history_entry = main_history_entry; } /* Calls `save_undo_history' if the current contents of the edit line is not * saved. */ void maybe_save_undo_history(void) { assert(undo_index <= undo_history.length); size_t save_undo_save_index = undo_save_index; undo_save_index = le_main_index; size_t len = active_length(); if (undo_history_entry == main_history_entry) { if (undo_index < undo_history.length) { struct undo_history *h = undo_history.contents[undo_index]; if (wcsncmp(le_main_buffer.contents, h->contents, len) == 0 && h->contents[len] == L'\0') { /* The contents of the main buffer is the same as saved in the * history. Just save the index. */ h->index = le_main_index; return; } undo_index++; } } else { if (wcsncmp(le_main_buffer.contents, main_history_value, len) == 0 && main_history_value[len] == L'\0') return; /* The contents of the buffer has been changed from the value of the * history entry, but it's not yet saved in the undo history. We first * save the original history value and then save the current buffer * contents. */ struct undo_history *h; pl_clear(&undo_history, free); h = xmallocs(sizeof *h, add(wcslen(main_history_value), 1), sizeof *h->contents); assert(save_undo_save_index <= wcslen(main_history_value)); h->index = save_undo_save_index; wcscpy(h->contents, main_history_value); pl_add(&undo_history, h); undo_index = 1; } save_undo_history(); } /* Applies the currently pending editing command to the range between the * current cursor index and the specified index. If no editing command is * pending, simply moves the cursor to the specified index. */ /* This function is used for all cursor-moving commands, even when not in the * vi mode. */ void exec_motion_command(size_t new_index, bool inclusive) { assert(le_main_index <= le_main_buffer.length); assert(new_index <= le_main_buffer.length); size_t old_index = le_main_index; size_t start_index, end_index; if (old_index <= new_index) start_index = old_index, end_index = new_index; else start_index = new_index, end_index = old_index; if (inclusive && end_index < le_main_buffer.length) end_index++; enum motion_expect_command_T mec = state.pending_command_motion; /* don't save undo history when repeating backspace */ bool repeated_backspace = (mec & MEC_DELETE) && (new_index + 1 == old_index) && current_command.func == cmd_backward_delete_char && last_command.func == cmd_backward_delete_char; if (!repeated_backspace) maybe_save_undo_history(); if (mec & MEC_COPY) { add_to_kill_ring(&le_main_buffer.contents[start_index], end_index - start_index); } if (mec & MEC_CASEMASK) { void (*case_func)(wchar_t *, size_t); INIT(case_func, 0); switch (mec & MEC_CASEMASK) { case MEC_UPPERCASE: case_func = to_upper_case; break; case MEC_LOWERCASE: case_func = to_lower_case; break; case MEC_SWITCHCASE: case_func = switch_case; break; } case_func(&le_main_buffer.contents[start_index], end_index - start_index); if (le_main_length < end_index) le_main_length = end_index; } switch (mec & MEC_CURSORMASK) { case MEC_TOSTART: le_main_index = start_index; break; case MEC_TOEND: le_main_index = end_index; break; case MEC_MOVE: le_main_index = new_index; break; } if (mec & MEC_DELETE) { save_current_edit_command(); clear_prediction(); if (!is_overwriting() || old_index <= new_index) wb_remove(&le_main_buffer, start_index, end_index - start_index); else restore_overwritten_buffer_contents(start_index, end_index); le_main_index = start_index; } if (mec & MEC_INSERT) { le_set_mode(LE_MODE_VI_INSERT); set_overwriting(false); } reset_state(); } /* Sets the specified motion expecting command as pending. * If the command is already pending, the command is executed on the whole * line. */ void set_motion_expect_command(enum motion_expect_command_T cmd) { if (state.pending_command_motion == MEC_MOVE) { state.count.multiplier = get_count(1); state.count.sign = 0; state.count.abs = 0; state.pending_command_motion = cmd; } else { if (state.pending_command_motion == cmd) exec_motion_expect_command_all(); else cmd_alert(L'\0'); } } /* Executes the specified motion expecting command with the specified motion * command. */ void exec_motion_expect_command( enum motion_expect_command_T cmd, le_command_func_T motion) { if (current_command.func != cmd_redo) ALERT_AND_RETURN_IF_PENDING; state.pending_command_motion = cmd; motion(L'\0'); } /* Executes the specified motion expecting command from the beginning to the end * of the line. */ void exec_motion_expect_command_line(enum motion_expect_command_T cmd) { if (current_command.func != cmd_redo) ALERT_AND_RETURN_IF_PENDING; state.pending_command_motion = cmd; exec_motion_expect_command_all(); } /* Executes the currently pending motion expecting command from the beginning to * the end of the line. */ void exec_motion_expect_command_all(void) { size_t save_index = le_main_index; enum motion_expect_command_T save_pending = state.pending_command_motion; le_main_index = 0; cmd_end_of_line(L'\0'); if (!(save_pending & (MEC_DELETE | MEC_CURSORMASK))) le_main_index = save_index; } /* Adds the specified string to the kill ring. * The maximum number of characters that are added is specified by `n'. */ void add_to_kill_ring(const wchar_t *s, size_t n) { if (n > 0 && s[0] != L'\0') { free(kill_ring[next_kill_index]); kill_ring[next_kill_index] = xwcsndup(s, n); next_kill_index = (next_kill_index + 1) % KILL_RING_SIZE; } } /* Sets the editing mode to "char expect" and the pending command to `cmd'. * The current editing mode is saved in `savemode'. */ void set_char_expect_command(le_command_func_T cmd) { savemode = LE_CURRENT_MODE; le_set_mode(LE_MODE_CHAR_EXPECT); state.pending_command_char = cmd; } /* Enables or disables the overwrite mode. */ void set_overwriting(bool overwrite) { free(overwrite_save_buffer.contents); if (overwrite) { size_t len = active_length(); overwrite_save_buffer.contents = xwcsndup(le_main_buffer.contents, len); overwrite_save_buffer.length = len; } else { overwrite_save_buffer.contents = NULL; } } /* Returns true iff the overwrite mode is active. */ bool is_overwriting(void) { return overwrite_save_buffer.contents != NULL; } /* Restores the main buffer contents that were overwritten in the current * overwrite mode. When called, `le_main_length >= le_main_buffer.length' must * hold. The caller must adjust `le_main_index' because this function may remove * some characters from `le_main_buffer'. */ void restore_overwritten_buffer_contents(size_t start_index, size_t end_index) { size_t mid_index; if (overwrite_save_buffer.length < start_index) mid_index = start_index; else if (overwrite_save_buffer.length > end_index) mid_index = end_index; else mid_index = overwrite_save_buffer.length; /* Restore contents from `start_index' to `mid_index' */ wmemcpy(&le_main_buffer.contents[start_index], &overwrite_save_buffer.contents[start_index], mid_index - start_index); /* Contents from `mid_index' to `end_index' were actually not overwritten * but appended, so they should be removed. */ wb_remove(&le_main_buffer, mid_index, end_index - mid_index); } /* Starts command history search by setting the editing mode to `mode' with * the specified direction `dir'. `mode' must be either LE_MODE_VI_SEARCH or * LE_MODE_EMACS_SEARCH. * The current editing mode is saved in `savemode'. */ void set_search_mode(le_mode_id_T mode, enum le_search_direction_T dir) { le_complete_cleanup(); savemode = LE_CURRENT_MODE; le_set_mode(mode); le_search_direction = dir; switch (mode) { case LE_MODE_VI_SEARCH: le_search_type = SEARCH_VI; break; case LE_MODE_EMACS_SEARCH: le_search_type = SEARCH_EMACS; break; default: assert(false); } wb_init(&le_search_buffer); update_search(); } /* Converts the first `n' characters of string `s' to upper case. * The string must be at least `n' characters long. */ void to_upper_case(wchar_t *s, size_t n) { for (size_t i = 0; i < n; i++) s[i] = towupper(s[i]); } /* Converts the first `n' characters of string `s' to lower case. * The string must be at least `n' characters long. */ void to_lower_case(wchar_t *s, size_t n) { for (size_t i = 0; i < n; i++) s[i] = towlower(s[i]); } /* Switches case of the first `n' characters of string `s'. * The string must be at least `n' characters long. */ void switch_case(wchar_t *s, size_t n) { for (size_t i = 0; i < n; i++) { wchar_t c = s[i]; s[i] = iswlower(c) ? towupper(c) : towlower(c); } } /********** Basic Commands **********/ /* Does nothing. */ void cmd_noop(wchar_t c __attribute__((unused))) { next_reset_completion = false; reset_state(); } /* Alerts. */ void cmd_alert(wchar_t c __attribute__((unused))) { lebuf_print_alert(true); reset_state(); } /* Inserts the character argument into the buffer. * If the count is set, inserts `count' times. * If `is_overwriting()' is true, overwrites the character instead of inserting. */ void cmd_self_insert(wchar_t c) { ALERT_AND_RETURN_IF_PENDING; if (c == L'\0') { cmd_alert(L'\0'); return; } clear_prediction(); int count = get_count(1); while (--count >= 0) if (is_overwriting() && le_main_index < le_main_buffer.length) le_main_buffer.contents[le_main_index++] = c; else wb_ninsert_force(&le_main_buffer, le_main_index++, &c, 1); reset_state(); } /* Inserts the tab character. */ void cmd_insert_tab(wchar_t c __attribute__((unused))) { cmd_self_insert(L'\t'); } /* Sets the `le_next_verbatim' flag. * The next character will be input to the main buffer even if it's a special * character. */ void cmd_expect_verbatim(wchar_t c __attribute__((unused))) { le_next_verbatim = true; } /* Adds the specified digit to the accumulating argument. */ /* If `c' is not a digit or a hyphen, does nothing. */ void cmd_digit_argument(wchar_t c) { if (L'0' <= c && c <= L'9') { if (state.count.abs > COUNT_ABS_MAX / 10) { cmd_alert(L'\0'); // argument too large return; } if (state.count.sign == 0) state.count.sign = 1; state.count.abs = state.count.abs * 10 + (unsigned) (c - L'0'); } else if (c == L'-') { if (state.count.sign == 0) state.count.sign = -1; else state.count.sign = -state.count.sign; } next_reset_completion = false; } /* If the count is not set, moves the cursor to the beginning of the line. * Otherwise, adds the given digit to the count. */ void cmd_bol_or_digit(wchar_t c) { if (state.count.sign == 0) cmd_beginning_of_line(c); else cmd_digit_argument(c); } /* Accepts the current line. * `le_editstate' is set to LE_EDITSTATE_DONE to induce line-editing to * terminate. * If history search is currently active, the search result is accepted. If the * search was failing, the line is not accepted. */ void cmd_accept_line(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; if (le_search_buffer.contents == NULL) { le_editstate = LE_EDITSTATE_DONE; reset_state(); } else { if (le_search_result != Histlist) le_editstate = LE_EDITSTATE_DONE; cmd_srch_accept_search(L'\0'); } } /* Aborts the current line. * `le_editstate' is set to LE_EDITSTATE_INTERRUPTED to induce line-editing to * terminate. */ void cmd_abort_line(wchar_t c __attribute__((unused))) { cmd_srch_abort_search(L'\0'); le_editstate = LE_EDITSTATE_INTERRUPTED; reset_state(); } /* Sets `le_editstate' to LE_EDITSTATE_ERROR. * The `le_readline' function will return INPUT_EOF. */ void cmd_eof(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; cmd_srch_abort_search(L'\0'); le_editstate = LE_EDITSTATE_ERROR; reset_state(); } /* If the edit line is empty, sets `le_editstate' to LE_EDITSTATE_ERROR (return * EOF). Otherwise, alerts. */ void cmd_eof_if_empty(wchar_t c __attribute__((unused))) { if (active_length() == 0) cmd_eof(L'\0'); else cmd_alert(L'\0'); } /* If the edit line is empty, sets `le_editstate' to LE_EDITSTATE_ERROR (return * EOF). Otherwise, deletes the character under the cursor. */ void cmd_eof_or_delete(wchar_t c __attribute__((unused))) { if (active_length() == 0) cmd_eof(L'\0'); else cmd_delete_char(L'\0'); } /* Inserts a hash sign ('#') at the beginning of the line and accepts the line. * If any count is set and the line already begins with a hash sign, the hash * sign is removed rather than added. The line is accepted anyway. */ void cmd_accept_with_hash(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); if (state.count.sign == 0 || le_main_buffer.contents[0] != L'#') wb_insert(&le_main_buffer, 0, L"#"); else wb_remove(&le_main_buffer, 0, 1); le_main_index = 0; cmd_accept_line(L'\0'); } /* Accept the current line including the prediction. */ void cmd_accept_prediction(wchar_t c) { cmd_accept_line(c); if (le_editstate == LE_EDITSTATE_DONE) le_main_length = SIZE_MAX; } /* Changes the editing mode to "vi insert". */ void cmd_setmode_viinsert(wchar_t c __attribute__((unused))) { set_mode(LE_MODE_VI_INSERT, false); } /* Changes the editing mode to "vi command". */ void cmd_setmode_vicommand(wchar_t c __attribute__((unused))) { set_mode(LE_MODE_VI_COMMAND, false); } /* Changes the editing mode to "emacs". */ void cmd_setmode_emacs(wchar_t c __attribute__((unused))) { set_mode(LE_MODE_EMACS, false); } /* Changes the editing mode to the specified one. */ void set_mode(le_mode_id_T newmode, bool overwrite) { ALERT_AND_RETURN_IF_PENDING; maybe_save_undo_history(); if (LE_CURRENT_MODE == LE_MODE_VI_INSERT && newmode == LE_MODE_VI_COMMAND) if (le_main_index > 0) le_main_index--; le_set_mode(newmode); set_overwriting(overwrite); reset_state(); } /* Executes the currently pending char-expecting command. */ void cmd_expect_char(wchar_t c) { if (!state.pending_command_char) { cmd_alert(L'\0'); return; } current_command.func = state.pending_command_char; current_command.arg = c; state.pending_command_char(c); } /* Cancels the currently pending char-expecting command. */ void cmd_abort_expect_char(wchar_t c __attribute__((unused))) { if (!state.pending_command_char) { cmd_alert(L'\0'); return; } le_set_mode(savemode); reset_state(); } /* Redraws everything. */ void cmd_redraw_all(wchar_t c __attribute__((unused))) { redraw_all(false); } /* Clears the screen and redraws everything at the top of the screen. */ void cmd_clear_and_redraw_all(wchar_t c __attribute__((unused))) { redraw_all(true); } void redraw_all(bool clear) { next_reset_completion = false; le_display_clear(clear); le_restore_terminal(); le_setupterm(false); le_set_terminal(); } /********** Motion Commands **********/ /* Invokes `cmd_alert' and returns true if the cursor is on the first character. */ bool alert_if_first(void) { if (le_main_index > 0) return false; cmd_alert(L'\0'); return true; } /* Invokes `cmd_alert' and returns true if the cursor is on the last character. */ bool alert_if_last(void) { if (LE_CURRENT_MODE == LE_MODE_VI_COMMAND) { if (state.pending_command_motion != MEC_MOVE) return false; if (le_main_buffer.length > 0 && le_main_index < le_main_buffer.length - 1) return false; } else { if (le_main_index < le_main_buffer.length) return false; } cmd_alert(L'\0'); return true; } /* Moves forward one character (or `count' characters if the count is set). */ /* exclusive motion command */ void cmd_forward_char(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_forward_char(count); else move_cursor_backward_char(-count); } /* Moves backward one character (or `count' characters if the count is set). */ /* exclusive motion command */ void cmd_backward_char(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_backward_char(count); else move_cursor_forward_char(-count); } /* Moves the cursor forward by `offset'. The `offset' must not be negative. */ void move_cursor_forward_char(int offset) { assert(offset >= 0); if (alert_if_last()) return; #if COUNT_ABS_MAX > SIZE_MAX if (offset > SIZE_MAX) offset = SIZE_MAX; #endif size_t new_index; if (le_main_buffer.length - le_main_index < (size_t) offset) new_index = le_main_buffer.length; else new_index = le_main_index + offset; exec_motion_command(new_index, false); } /* Moves the cursor backward by `offset'. The `offset' must not be negative. */ void move_cursor_backward_char(int offset) { assert(offset >= 0); if (alert_if_first()) return; size_t new_index; #if COUNT_ABS_MAX > SIZE_MAX if ((int) le_main_index <= offset) #else if (le_main_index <= (size_t) offset) #endif new_index = 0; else new_index = le_main_index - offset; exec_motion_command(new_index, false); } /* Moves forward one bigword (or `count' bigwords if the count is set). */ /* exclusive motion command */ void cmd_forward_bigword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_forward_bigword(count); else move_cursor_backward_bigword(-count); } /* Moves the cursor to the end of the current bigword (or the next bigword if * already at the end). If the count is set, moves to the end of `count'th * bigword. */ /* inclusive motion command */ void cmd_end_of_bigword(wchar_t c __attribute__((unused))) { if (alert_if_last()) return; int count = get_count(1); size_t new_index = le_main_index; while (--count >= 0 && new_index < le_main_buffer.length) new_index = next_end_of_bigword_index( le_main_buffer.contents, new_index, true); exec_motion_command(new_index, true); } /* Moves backward one bigword (or `count' bigwords if the count is set). */ /* exclusive motion command */ void cmd_backward_bigword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_backward_bigword(count); else move_cursor_forward_bigword(-count); } /* Moves the cursor forward `count' bigwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_forward_bigword(int count) { if (alert_if_last()) return; size_t new_index = le_main_index; if (!need_cw_treatment()) { while (count-- > 0 && new_index < le_main_buffer.length) new_index = next_bigword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } else { while (count > 1 && new_index < le_main_buffer.length) { new_index = next_bigword_index(le_main_buffer.contents, new_index); count--; } if (count > 0 && new_index < le_main_buffer.length) { new_index = next_end_of_bigword_index( le_main_buffer.contents, new_index, false); } exec_motion_command(new_index, true); } } /* Moves the cursor backward `count' bigwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_backward_bigword(int count) { if (alert_if_first()) return; size_t new_index = le_main_index; while (count-- > 0 && new_index > 0) new_index = previous_bigword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } /* Returns the index of the next bigword in string `s', counted from index `i'. * The return value is greater than `i' unless `s[i]' is a null character. */ /* A bigword is a sequence of non-blank characters. */ size_t next_bigword_index(const wchar_t *s, size_t i) { while (s[i] != L'\0' && !iswblank(s[i])) i++; while (s[i] != L'\0' && iswblank(s[i])) i++; return i; } /* Returns the index of the end of the current bigword in string `s', counted * from index `i'. If `i' is at the end of the bigword and `progress' is true, * the end of the next bigword is returned. * The return value is greater than `i' unless `s[i]' is a null character. */ size_t next_end_of_bigword_index(const wchar_t *s, size_t i, bool progress) { const size_t init = i; start: if (s[i] == L'\0') return i; while (s[i] != L'\0' && iswblank(s[i])) i++; while (s[i] != L'\0' && !iswblank(s[i])) i++; i--; if (i > init || !progress) { return i; } else { i++; goto start; } } /* Returns the index of the previous bigword in string `s', counted from index * `i'. The return value is less than `i' unless `i' is zero. */ size_t previous_bigword_index(const wchar_t *s, size_t i) { const size_t init = i; start: while (i > 0 && iswblank(s[i])) i--; while (i > 0 && !iswblank(s[i])) i--; if (i == 0) return i; i++; if (i < init) { return i; } else { i--; goto start; } } /* Moves forward one semiword (or `count' semiwords if the count is set). */ /* exclusive motion command */ void cmd_forward_semiword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_forward_semiword(count); else move_cursor_backward_semiword(-count); } /* Moves the cursor to the end of the current semiword (or the next semiword if * already at the end). If the count is set, moves to the end of the `count'th * semiword. */ /* inclusive motion command */ void cmd_end_of_semiword(wchar_t c __attribute__((unused))) { if (alert_if_last()) return; int count = get_count(1); size_t new_index = le_main_index; while (--count >= 0 && new_index < le_main_buffer.length) new_index = next_end_of_semiword_index( le_main_buffer.contents, new_index, true); exec_motion_command(new_index, true); } /* Moves backward one semiword (or `count' semiwords if the count is set). */ /* exclusive motion command */ void cmd_backward_semiword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_backward_semiword(count); else move_cursor_forward_semiword(-count); } /* Moves the cursor forward `count' semiwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_forward_semiword(int count) { if (alert_if_last()) return; size_t new_index = le_main_index; if (!need_cw_treatment()) { while (count-- > 0 && new_index < le_main_buffer.length) new_index = next_semiword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } else { while (count > 1 && new_index < le_main_buffer.length) { new_index = next_semiword_index(le_main_buffer.contents, new_index); count--; } if (count > 0 && new_index < le_main_buffer.length) { new_index = next_end_of_semiword_index( le_main_buffer.contents, new_index, false); } exec_motion_command(new_index, true); } } /* Moves the cursor backward `count' semiwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_backward_semiword(int count) { if (alert_if_first()) return; size_t new_index = le_main_index; while (count-- > 0 && new_index > 0) new_index = previous_semiword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } /* Returns the index of the next semiword in string `s', counted from index `i'. * The return value is greater than `i' unless `s[i]' is a null character. */ /* A "semiword" is a sequence of characters that are not or . */ size_t next_semiword_index(const wchar_t *s, size_t i) { while (s[i] != L'\0' && !iswblank(s[i]) && !iswpunct(s[i])) i++; while (s[i] != L'\0' && (iswblank(s[i]) || iswpunct(s[i]))) i++; return i; } /* Returns the index of the end of the current semiword in string `s', counted * from index `i'. If `i' is at the end of the semiword and `progress' is true, * the end of the next semiword is returned. * The return value is greater than `i' unless `s[i]' is a null character. */ size_t next_end_of_semiword_index(const wchar_t *s, size_t i, bool progress) { const size_t init = i; start: if (s[i] == L'\0') return i; while (s[i] != L'\0' && (iswblank(s[i]) || iswpunct(s[i]))) i++; while (s[i] != L'\0' && !iswblank(s[i]) && !iswpunct(s[i])) i++; i--; if (i > init || !progress) { return i; } else { i++; goto start; } } /* Returns the index of the previous semiword in string `s', counted from index * `i'. The return value is less than `i' unless `i' is zero. */ size_t previous_semiword_index(const wchar_t *s, size_t i) { const size_t init = i; start: while (i > 0 && (iswblank(s[i]) || iswpunct(s[i]))) i--; while (i > 0 && !iswblank(s[i]) && !iswpunct(s[i])) i--; if (i == 0) return i; i++; if (i < init) { return i; } else { i--; goto start; } } /* Moves forward one viword (or `count' viwords if the count is set). */ /* exclusive motion command */ void cmd_forward_viword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_forward_viword(count); else move_cursor_backward_viword(-count); } /* Moves the cursor to the end of the current viword (or the next viword if * already at the end). If the count is set, moves to the end of the `count'th * viword. */ /* inclusive motion command */ void cmd_end_of_viword(wchar_t c __attribute__((unused))) { if (alert_if_last()) return; int count = get_count(1); size_t new_index = le_main_index; while (--count >= 0 && new_index < le_main_buffer.length) new_index = next_end_of_viword_index( le_main_buffer.contents, new_index, true); exec_motion_command(new_index, true); } /* Moves backward one viword (or `count' viwords if the count is set). */ /* exclusive motion command */ void cmd_backward_viword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_backward_viword(count); else move_cursor_forward_viword(-count); } /* Moves the cursor forward `count' viwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_forward_viword(int count) { if (alert_if_last()) return; size_t new_index = le_main_index; if (!need_cw_treatment()) { while (count-- > 0 && new_index < le_main_buffer.length) new_index = next_viword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } else { while (count > 1 && new_index < le_main_buffer.length) { new_index = next_viword_index(le_main_buffer.contents, new_index); count--; } if (count > 0 && new_index < le_main_buffer.length) { new_index = next_end_of_viword_index( le_main_buffer.contents, new_index, false); } exec_motion_command(new_index, true); } } /* Checks if we need a special treatment for the "cw" and "cW" commands. */ bool need_cw_treatment(void) { return (state.pending_command_motion & MEC_INSERT) && !iswblank(le_main_buffer.contents[le_main_index]); } /* Moves the cursor backward `count' viwords. * If `count' is negative, the cursor is not moved. */ void move_cursor_backward_viword(int count) { if (alert_if_first()) return; size_t new_index = le_main_index; while (count-- > 0 && new_index > 0) new_index = previous_viword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } /* Returns the index of the next viword in string `s', counted from index `i'. * The return value is greater than `i' unless `s[i]' is a null character. */ /* A viword is a sequence either of alphanumeric characters and underscores or * of other non-blank characters. */ size_t next_viword_index(const wchar_t *s, size_t i) { if (s[i] == L'_' || iswalnum(s[i])) { do i++; while (s[i] == L'_' || iswalnum(s[i])); if (!iswblank(s[i])) return i; } else { while (!iswblank(s[i])) { if (s[i] == L'\0') return i; i++; if (s[i] == L'_' || iswalnum(s[i])) return i; } } do i++; while (iswblank(s[i])); return i; } /* Returns the index of the end of the current viword in string `s', counted * from index `i'. * If `progress' is true: * If `i' is at the end of the viword, the end of the next viword is returned. * The return value is greater than `i' unless `s[i]' is a null character. * If `progress' is false: * If `i' is at the end of the viword, `i' is returned. */ size_t next_end_of_viword_index(const wchar_t *s, size_t i, bool progress) { const size_t init = i; start: while (iswblank(s[i])) i++; if (s[i] == L'\0') return i; if (s[i] == L'_' || iswalnum(s[i])) { do i++; while (s[i] == L'_' || iswalnum(s[i])); } else { do i++; while (s[i] != L'\0' && s[i] != L'_' && !iswblank(s[i]) && !iswalnum(s[i])); } i--; if (i > init || !progress) { return i; } else { i++; goto start; } } /* Returns the index of the previous viword in string `s', counted form index * `i'. The return value is less than `i' unless `i' is zero. */ size_t previous_viword_index(const wchar_t *s, size_t i) { const size_t init = i; start: while (i > 0 && iswblank(s[i])) i--; if (s[i] == L'_' || iswalnum(s[i])) { do { if (i == 0) return 0; i--; } while (s[i] == L'_' || iswalnum(s[i])); } else { do { if (i == 0) return 0; i--; } while (s[i] != L'_' && !iswblank(s[i]) && !iswalnum(s[i])); } i++; if (i < init) { return i; } else { i--; goto start; } } /* Moves to the next emacsword (or the `count'th emacsword if the count is set). */ /* exclusive motion command */ void cmd_forward_emacsword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_forward_emacsword(count); else move_cursor_backward_emacsword(-count); } /* Moves backward one emacsword (or `count' emacswords if the count is set). */ /* exclusive motion command */ void cmd_backward_emacsword(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count >= 0) move_cursor_backward_emacsword(count); else move_cursor_forward_emacsword(-count); } /* Moves the cursor to the `count'th emacsword. * If `count' is negative, the cursor is not moved. */ void move_cursor_forward_emacsword(int count) { size_t new_index = le_main_index; while (count-- > 0 && new_index < le_main_buffer.length) new_index = next_emacsword_index(le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } /* Moves the cursor backward `count'th emacsword. * If `count' is negative, the cursor is not moved. */ void move_cursor_backward_emacsword(int count) { size_t new_index = le_main_index; while (count-- > 0 && new_index > 0) new_index = previous_emacsword_index( le_main_buffer.contents, new_index); exec_motion_command(new_index, false); } /* Returns the index of the next emacsword in string `s', counted from index * `i'. The return value is greater than `i' unless `s[i]' is a null character. */ /* An emacsword is a sequence of non-alphanumeric characters. */ size_t next_emacsword_index(const wchar_t *s, size_t i) { while (s[i] != L'\0' && !iswalnum(s[i])) i++; while (s[i] != L'\0' && iswalnum(s[i])) i++; return i; } /* Returns the index of the previous emacsword in string `s', counted from * index `i'. The return value is less than `i' unless `i' is zero. */ /* An emacsword is a sequence of alphanumeric characters. */ size_t previous_emacsword_index(const wchar_t *s, size_t i) { const size_t init = i; start: while (i > 0 && !iswalnum(s[i])) i--; while (i > 0 && iswalnum(s[i])) i--; if (i == 0) return i; i++; if (i < init) { return i; } else { i--; goto start; } } /* Moves the cursor to the beginning of the line. */ /* exclusive motion command */ void cmd_beginning_of_line(wchar_t c __attribute__((unused))) { exec_motion_command(0, false); } /* Moves the cursor to the end of the line. */ /* inclusive motion command */ void cmd_end_of_line(wchar_t c __attribute__((unused))) { exec_motion_command(le_main_buffer.length, true); } /* Moves the cursor to the `count'th character in the edit line. * If the count is not set, moves to the beginning of the line. If the count is * negative, moves to the `le_main_buffer.length + count'th character. */ /* exclusive motion command */ void cmd_go_to_column(wchar_t c __attribute__((unused))) { int index = get_count(0); if (index >= 0) { if (index > 0) index--; #if COUNT_ABS_MAX > SIZE_MAX if (index > (int) le_main_buffer.length) #else if ((size_t) index > le_main_buffer.length) #endif index = le_main_buffer.length; } else { #if COUNT_ABS_MAX > SIZE_MAX if (-index > (int) le_main_buffer.length) #else if ((size_t) -index > le_main_buffer.length) #endif index = 0; else index = (int) le_main_buffer.length + index; } exec_motion_command((size_t) index, false); } /* Moves the cursor to the first non-blank character. */ /* exclusive motion command */ void cmd_first_nonblank(wchar_t c __attribute__((unused))) { size_t i = 0; while (c = le_main_buffer.contents[i], c != L'\0' && iswblank(c)) i++; exec_motion_command(i, false); } /* Sets the editing mode to "char expect" and the pending command to * `find_char'. */ void cmd_find_char(wchar_t c __attribute__((unused))) { maybe_save_undo_history(); set_char_expect_command(find_char); } /* Moves the cursor to the `count'th occurrence of `c' after the current * position. */ /* inclusive motion command */ void find_char(wchar_t c) { exec_find(c, get_count(1), false); } /* Sets the editing mode to "char expect" and the pending command to * `find_char_rev'. */ void cmd_find_char_rev(wchar_t c __attribute__((unused))) { maybe_save_undo_history(); set_char_expect_command(find_char_rev); } /* Moves the cursor to the `count'th occurrence of `c' before the current * position. */ /* exclusive motion command */ void find_char_rev(wchar_t c) { exec_find(c, -get_count(1), false); } /* Sets the editing mode to "char expect" and the pending command to * `till_char'. */ void cmd_till_char(wchar_t c __attribute__((unused))) { maybe_save_undo_history(); set_char_expect_command(till_char); } /* Moves the cursor to the character just before `count'th occurrence of `c' * after the current position. */ /* inclusive motion command */ void till_char(wchar_t c) { exec_find(c, get_count(1), true); } /* Sets the editing mode to "char expect" and the pending command to * `till_char_rev'. */ void cmd_till_char_rev(wchar_t c __attribute__((unused))) { maybe_save_undo_history(); set_char_expect_command(till_char_rev); } /* Moves the cursor to the character just after `count'th occurrence of `c' * before the current position. */ /* exclusive motion command */ void till_char_rev(wchar_t c) { exec_find(c, -get_count(1), true); } /* Executes the find/till command. */ void exec_find(wchar_t c, int count, bool till) { le_set_mode(savemode); save_current_find_command(); size_t new_index = find_nth_occurence(c, count); if (new_index == SIZE_MAX) goto error; if (till) { if (new_index >= le_main_index) { if (new_index == 0) goto error; new_index--; } else { if (new_index == le_main_buffer.length) goto error; new_index++; } } exec_motion_command(new_index, new_index >= le_main_index); return; error: cmd_alert(L'\0'); return; } /* Finds the position of the `n'th occurrence of `c' in the edit line from the * current position. Returns `SIZE_MAX' on failure (no such occurrence). */ size_t find_nth_occurence(wchar_t c, int n) { size_t i = le_main_index; if (n == 0) { return i; } else if (c == L'\0') { return SIZE_MAX; /* no such occurrence */ } else if (n >= 0) { while (n > 0 && i < le_main_buffer.length) { i++; if (le_main_buffer.contents[i] == c) n--; } } else { while (n < 0 && i > 0) { i--; if (le_main_buffer.contents[i] == c) n++; } } if (n != 0) return SIZE_MAX; /* no such occurrence */ else return i; } /* Redoes the last find/till command. */ void cmd_refind_char(wchar_t c __attribute__((unused))) { if (!last_find_command.func) { cmd_alert(L'\0'); return; } last_find_command.func(last_find_command.arg); } /* Redoes the last find/till command in the reverse direction. */ void cmd_refind_char_rev(wchar_t c __attribute__((unused))) { if (!last_find_command.func) { cmd_alert(L'\0'); return; } if (state.count.sign == 0) state.count.sign = -1, state.count.abs = 1; else if (state.count.sign >= 0) state.count.sign = -1; else state.count.sign = 1; last_find_command.func(last_find_command.arg); } /********** Editing Commands **********/ /* Removes the character under the cursor. * If the count is set, `count' characters are killed. */ void cmd_delete_char(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_forward_char); } /* Removes the bigword after the cursor. * If the count is set, `count' bigwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_delete_bigword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_forward_bigword); } /* Removes the semiword after the cursor. * If the count is set, `count' semiwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_delete_semiword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_forward_semiword); } /* Removes the viword after the cursor. * If the count is set, `count' viwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_delete_viword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_forward_viword); } /* Removes the emacsword after the cursor. * If the count is set, `count' emacswords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_delete_emacsword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_forward_emacsword); } /* Removes the character behind the cursor. * If the count is set, `count' characters are killed. */ void cmd_backward_delete_char(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_backward_char); } /* Removes the bigword behind the cursor. * If the count is set, `count' bigwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_delete_bigword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_backward_bigword); } /* Removes the semiword behind the cursor. * If the count is set, `count' semiwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_delete_semiword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_backward_semiword); } /* Removes the viword behind the cursor. * If the count is set, `count' viwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_delete_viword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_backward_viword); } /* Removes the emacsword behind the cursor. * If the count is set, `count' emacswords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_delete_emacsword(wchar_t c __attribute__((unused))) { enum motion_expect_command_T cmd; cmd = (state.count.sign == 0) ? MEC_DELETE : MEC_KILL; exec_motion_expect_command(cmd, cmd_backward_emacsword); } /* Removes all characters in the edit line. */ void cmd_delete_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_DELETE); } /* Removes all characters after the cursor. */ void cmd_forward_delete_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_DELETE, cmd_end_of_line); } /* Removes all characters behind the cursor. */ void cmd_backward_delete_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_DELETE, cmd_beginning_of_line); } /* Kills the character under the cursor. * If the count is set, `count' characters are killed. */ void cmd_kill_char(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_forward_char); } /* Kills the bigword after the cursor. * If the count is set, `count' bigwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_kill_bigword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_forward_bigword); } /* Kills the semiword after the cursor. * If the count is set, `count' semiwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_kill_semiword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_forward_semiword); } /* Kills the viword after the cursor. * If the count is set, `count' viwords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_kill_viword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_forward_viword); } /* Kills the emacsword after the cursor. * If the count is set, `count' emacswords are killed. * If the cursor is at the end of the line, the terminal is alerted. */ void cmd_kill_emacsword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_forward_emacsword); } /* Kills the character behind the cursor. * If the count is set, `count' characters are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_kill_char(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_backward_char); } /* Kills the bigword behind the cursor. * If the count is set, `count' bigwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_kill_bigword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_backward_bigword); } /* Kills the semiword behind the cursor. * If the count is set, `count' semiwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_kill_semiword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_backward_semiword); } /* Kills the viword behind the cursor. * If the count is set, `count' viwords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_kill_viword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_backward_viword); } /* Kills the emacsword behind the cursor. * If the count is set, `count' emacswords are killed. * If the cursor is at the beginning of the line, the terminal is alerted. */ void cmd_backward_kill_emacsword(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_backward_emacsword); } /* Kills all characters in the edit line. */ void cmd_kill_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_KILL); } /* Kills all characters after the cursor. */ void cmd_forward_kill_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_end_of_line); } /* Kills all characters before the cursor. */ void cmd_backward_kill_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_beginning_of_line); } /* Inserts the last-killed string before the cursor. * If the count is set, inserts `count' times. * The cursor is left on the last character inserted. */ void cmd_put_before(wchar_t c __attribute__((unused))) { put_killed_string(false, true); } /* Inserts the last-killed string after the cursor. * If the count is set, inserts `count' times. * The cursor is left on the last character inserted. */ void cmd_put(wchar_t c __attribute__((unused))) { put_killed_string(true, true); } /* Inserts the last-killed string before the cursor. * If the count is set, inserts `count' times. * The cursor is left after the inserted string. */ void cmd_put_left(wchar_t c __attribute__((unused))) { put_killed_string(false, false); } /* Inserts the last-killed text at the current cursor position (`count' times). * If `after_cursor' is true, the text is inserted after the current cursor * position. Otherwise, before the current position. * If `cursor_on_last_char' is true, the cursor is left on the last character * inserted. Otherwise, the cursor is left after the inserted text. */ void put_killed_string(bool after_cursor, bool cursor_on_last_char) { ALERT_AND_RETURN_IF_PENDING; save_current_edit_command(); maybe_save_undo_history(); size_t index = (next_kill_index - 1) % KILL_RING_SIZE; if (kill_ring[index] == NULL) { cmd_alert(L'\0'); return; } insert_killed_string(after_cursor, cursor_on_last_char, index); } /* Inserts the killed text at the current cursor position (`count' times). * If `after_cursor' is true, the text is inserted after the current cursor * position. Otherwise, before the current position. * If `cursor_on_last_char' is true, the cursor is left on the last character * inserted. Otherwise, the cursor is left after the inserted text. * `index' specifies the text in the kill ring to be inserted. If the text * does not exist at the specified index in the kill ring, this function does * nothing. */ void insert_killed_string( bool after_cursor, bool cursor_on_last_char, size_t index) { clear_prediction(); const wchar_t *s = kill_ring[index]; if (s == NULL) return; last_put_elem = index; if (after_cursor && le_main_index < le_main_buffer.length) le_main_index++; size_t offset = le_main_buffer.length - le_main_index; for (int count = get_count(1); --count >= 0; ) wb_insert(&le_main_buffer, le_main_index, s); assert(le_main_buffer.length >= offset + 1); last_put_range_start = le_main_index; le_main_index = le_main_buffer.length - offset; last_put_range_length = le_main_index - last_put_range_start; if (cursor_on_last_char) le_main_index--; reset_state(); } /* Replaces the string just inserted by `cmd_put_left' with the previously * killed string. */ void cmd_put_pop(wchar_t c __attribute__((unused))) { static bool last_success = false; ALERT_AND_RETURN_IF_PENDING; if ((last_command.func != cmd_put_left && last_command.func != cmd_put && last_command.func != cmd_put_before && (last_command.func != cmd_put_pop || !last_success)) || kill_ring[last_put_elem] == NULL) { last_success = false; cmd_alert(L'\0'); return; } last_success = true; save_current_edit_command(); maybe_save_undo_history(); clear_prediction(); size_t index = last_put_elem; do index = (index - 1) % KILL_RING_SIZE; while (kill_ring[index] == NULL); /* Remove the just inserted text. */ assert(last_put_range_start <= le_main_buffer.length); wb_remove(&le_main_buffer, last_put_range_start, last_put_range_length); le_main_index = last_put_range_start; insert_killed_string(false, false, index); } /* Undoes the last editing command. */ void cmd_undo(wchar_t c __attribute__((unused))) { cancel_undo(-get_count(1)); } /* Undoes all changes to the edit line. */ void cmd_undo_all(wchar_t c __attribute__((unused))) { cancel_undo(-COUNT_ABS_MAX); } /* Cancels the last undo. */ void cmd_cancel_undo(wchar_t c __attribute__((unused))) { cancel_undo(get_count(1)); } /* Cancels all previous undo. */ void cmd_cancel_undo_all(wchar_t c __attribute__((unused))) { cancel_undo(COUNT_ABS_MAX); } /* Performs "undo"/"cancel undo". * `undo_index' is increased by `offset' and the contents of the history entry * of the new index is set to the edit line. * `offset' must be between `-COUNT_ABS_MAX' and `COUNT_ABS_MAX'. */ void cancel_undo(int offset) { maybe_save_undo_history(); clear_prediction(); if (undo_history_entry != main_history_entry) goto error; if (offset < 0) { if (undo_index == 0) goto error; #if COUNT_ABS_MAX > SIZE_MAX if (-offset > (int) undo_index) #else if ((size_t) -offset > undo_index) #endif undo_index = 0; else undo_index += offset; } else { if (undo_index + 1 >= undo_history.length) goto error; #if COUNT_ABS_MAX > SIZE_MAX if (offset >= (int) (undo_history.length - undo_index)) #else if ((size_t) offset >= undo_history.length - undo_index) #endif undo_index = undo_history.length - 1; else undo_index += offset; } const struct undo_history *entry = undo_history.contents[undo_index]; wb_replace(&le_main_buffer, 0, SIZE_MAX, entry->contents, SIZE_MAX); assert(entry->index <= le_main_buffer.length); le_main_index = entry->index; reset_state(); return; error: cmd_alert(L'\0'); return; } /* Redoes the last editing command. */ /* XXX: currently vi's "i" command cannot be redone. */ void cmd_redo(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; if (!last_edit_command.command.func) { cmd_alert(L'\0'); return; } if (state.count.sign != 0) last_edit_command.state.count = state.count; state = last_edit_command.state; last_edit_command.command.func(last_edit_command.command.arg); } /********** Completion Commands **********/ /* Performs command line completion. */ void cmd_complete(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete(lecr_normal); reset_state(); } /* Selects the next completion candidate. * If the count is set, selects the `count'th next candidate. */ void cmd_complete_next_candidate(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_candidate(get_count(1)); reset_state(); } /* Selects the previous completion candidate. * If the count is set, selects the `count'th previous candidate. */ void cmd_complete_prev_candidate(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_candidate(-get_count(1)); reset_state(); } /* Selects the first candidate in the next column. * If the count is set, selects that of the `count'th next column. */ void cmd_complete_next_column(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_column(get_count(1)); reset_state(); } /* Selects the first candidate in the previous column. * If the count is set, selects that of the `count'th previous column. */ void cmd_complete_prev_column(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_column(-get_count(1)); reset_state(); } /* Selects the first candidate in the next page. * If the count is set, selects that of the `count'th next page. */ void cmd_complete_next_page(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_page(get_count(1)); reset_state(); } /* Selects the first candidate in the previous page. * If the count is set, selects that of the `count'th previous page. */ void cmd_complete_prev_page(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete_select_page(-get_count(1)); reset_state(); } /* Performs command line completion and * * if the count is not set, list all the candidates without changing the * main buffer. * * if the count is set, complete the `count'th candidate. */ void cmd_complete_list(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; maybe_save_undo_history(); clear_prediction(); le_complete_cleanup(); /* leave `next_reset_completion' to be true because the results of this * command cannot be used by succeeding completion commands. */ // next_reset_completion = false; le_complete_fix_candidate(get_count(0)); reset_state(); } /* Performs command line completion and replaces the current word with all of * the generated candidates. */ void cmd_complete_all(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete(lecr_substitute_all_candidates); reset_state(); } /* Performs command line completion and replaces the current word with the * longest common prefix of the candidates. */ void cmd_complete_max(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete(lecr_longest_common_prefix); reset_state(); } /* Like `cmd_complete_max' for a first key stroke, then like `cmd_complete'. */ void cmd_complete_max_then_list(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); le_complete(last_command.func != cmd_complete_max_then_list ? lecr_longest_common_prefix : lecr_normal); reset_state(); } /* Like `cmd_complete_max' for a first key stroke, then like * `cmd_complete_next_candidate'. */ void cmd_complete_max_then_next_candidate(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); if (last_command.func != cmd_complete_max_then_next_candidate) le_complete(lecr_longest_common_prefix); else le_complete_select_candidate(get_count(1)); reset_state(); } /* Like `cmd_complete_max' for a first key stroke, then like * `cmd_complete_prev_candidate'. */ void cmd_complete_max_then_prev_candidate(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); if (last_command.func != cmd_complete_max_then_prev_candidate) le_complete(lecr_longest_common_prefix); else le_complete_select_candidate(-get_count(1)); reset_state(); } /* Clears the current candidates. */ void cmd_clear_candidates(wchar_t c __attribute__((unused))) { le_complete_cleanup(); } void check_reset_completion(void) { if (reset_completion) { maybe_save_undo_history(); le_complete_cleanup(); reset_completion = false; } next_reset_completion = false; } /********** Prediction Commands **********/ #ifndef MAX_PREDICTION_SAMPLE #define MAX_PREDICTION_SAMPLE 10000 #endif /* ifndef MAX_PREDICTION_SAMPLE */ /* Create a probability distribution tree for command prediction based on the * current history. The result is set to `prediction_tree'. */ void create_prediction_tree(void) { trie_T *t = trie_create(); #define N 4 size_t hits[N] = {0}; for (const histlink_T *l = Histlist; (l = l->prev) != Histlist; ) { const histentry_T *e = (const histentry_T *) l; size_t k = count_matching_previous_commands(e); assert(k < N); for (size_t i = 0; i <= k; i++) hits[i]++; if (hits[0] >= MAX_PREDICTION_SAMPLE) break; wchar_t *cmd = malloc_mbstowcs(e->value); if (cmd == NULL) continue; t = trie_add_probability(t, cmd, 1.0 / (hits[k] + 1)); free(cmd); } prediction_tree = t; } // Counts N-1 at most size_t count_matching_previous_commands(const histentry_T *e) { size_t count = 0; const histlink_T *l1 = &e->link, *l2 = Histlist; while ((l1 = l1->prev) != Histlist) { l2 = l2->prev; const histentry_T *e1 = (const histentry_T *) l1; const histentry_T *e2 = (const histentry_T *) l2; if (strcmp(e1->value, e2->value) != 0) break; count++; if (count >= N - 1) break; } return count; } #undef N /* Clears the second part of `le_main_buffer'. * Commands that modify the buffer usually need to call this function. However, * if a command affects or is affected by the second part, the command might * need to update `le_main_length' in a more sophisticated way rather than * calling this function. */ void clear_prediction(void) { if (le_main_length < le_main_buffer.length) wb_truncate(&le_main_buffer, le_main_length); le_main_length = SIZE_MAX; } /* Removes any existing prediction and, if the cursor is at the end of line, * appends a new prediction to the main buffer. */ void update_buffer_with_prediction(void) { clear_prediction(); if (!shopt_le_predictempty && active_length() == 0) return; if (le_main_index < active_length()) return; le_main_length = le_main_buffer.length; wchar_t *suffix = trie_probable_key( prediction_tree, le_main_buffer.contents); wb_catfree(&le_main_buffer, suffix); } /********** Vi-Mode Specific Commands **********/ /* Sets the editing mode to "vi expect" and the pending command to * `vi_replace_char'. */ void cmd_vi_replace_char(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_char_expect_command(vi_replace_char); } /* Replaces the character under the cursor with `c'. * If the count is set, the `count' characters are replaced. */ void vi_replace_char(wchar_t c) { save_current_edit_command(); le_set_mode(savemode); if (c != L'\0') { int count = get_count(1); if (count > 0 && le_main_index < le_main_buffer.length) { do { le_main_buffer.contents[le_main_index] = c; count--, le_main_index++; } while (count > 0 && le_main_index < le_main_buffer.length); if (le_main_length < le_main_index) le_main_length = le_main_index; clear_prediction(); le_main_index--; } reset_state(); } else { cmd_alert(L'\0'); } } /* Moves the cursor to the beginning of the line and sets the editing mode to * "vi insert". */ void cmd_vi_insert_beginning(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_INSERT | MEC_TOSTART); } /* Moves the cursor forward one character and sets the editing mode to "vi * insert". */ void cmd_vi_append(wchar_t c __attribute__((unused))) { reset_count(); exec_motion_expect_command(MEC_INSERT | MEC_MOVE, cmd_forward_char); } /* Moves the cursor to the end of the line and sets the editing mode to "vi * insert".*/ void cmd_vi_append_to_eol(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_INSERT | MEC_TOEND); } /* Sets the editing mode to "vi insert" and starts the overwrite mode. */ void cmd_vi_replace(wchar_t c __attribute__((unused))) { set_mode(LE_MODE_VI_INSERT, true); } /* Sets the pending command to MEC_SWITCHCASE. * The count multiplier is set to the current count. * If the pending command is already set to MEC_SWITCHCASE, the whole line is * switch-cased. */ void cmd_vi_switch_case(wchar_t c __attribute__((unused))) { set_motion_expect_command(MEC_SWITCHCASE | MEC_TOSTART); } /* Switches the case of the character under the cursor and advances the cursor. * If the count is set, `count' characters are changed. */ void cmd_vi_switch_case_char(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_SWITCHCASE | MEC_TOEND, cmd_forward_char); } /* Sets the pending command to MEC_COPY. * The count multiplier is set to the current count. * If the pending command is already set to MEC_COPY, the whole line is copied * to the kill ring. */ void cmd_vi_yank(wchar_t c __attribute__((unused))) { set_motion_expect_command(MEC_COPY); } /* Copies the content of the edit line from the current position to the end. */ void cmd_vi_yank_to_eol(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_COPY, cmd_end_of_line); } /* Sets the pending command to MEC_KILL. * The count multiplier is set to the current count. * If the pending command is already set to MEC_KILL, the whole line is moved * to the kill ring. */ void cmd_vi_delete(wchar_t c __attribute__((unused))) { set_motion_expect_command(MEC_KILL); } /* Deletes the content of the edit line from the current position to the end and * put it in the kill ring. */ /* cmd_vi_delete_to_eol is the same as cmd_forward_kill_line. void cmd_vi_delete_to_eol(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_KILL, cmd_end_of_line); } */ /* Sets the pending command to MEC_CHANGE. * The count multiplier is set to the current count. * If the pending command is already set to MEC_CHANGE, the whole line is * deleted and the editing mode is set to "vi insert". */ void cmd_vi_change(wchar_t c __attribute__((unused))) { set_motion_expect_command(MEC_CHANGE); } /* Deletes the content of the edit line from the current position to the end and * sets the editing mode to "vi insert". */ void cmd_vi_change_to_eol(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_CHANGE, cmd_end_of_line); } /* Deletes all the content of the edit line and sets the editing mode to "vi * insert". */ void cmd_vi_change_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_CHANGE); } /* Sets the pending command to `MEC_COPYCHANGE'. * The count multiplier is set to the current count. * If the pending command is already set to `MEC_COPYCHANGE', the whole line is * moved to the kill ring and the editing mode is set to "vi insert". */ void cmd_vi_yank_and_change(wchar_t c __attribute__((unused))) { set_motion_expect_command(MEC_COPYCHANGE); } /* Deletes the content of the edit line from the current position to the end, * put it in the kill ring, and sets the editing mode to "vi insert". */ void cmd_vi_yank_and_change_to_eol(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_COPYCHANGE, cmd_end_of_line); } /* Deletes all the content of the edit line, put it in the kill ring, and sets * the editing mode to "vi insert". */ void cmd_vi_yank_and_change_line(wchar_t c __attribute__((unused))) { exec_motion_expect_command_line(MEC_COPYCHANGE); } /* Kills the character under the cursor and sets the editing mode to * "vi insert". If the count is set, `count' characters are killed. */ void cmd_vi_substitute(wchar_t c __attribute__((unused))) { exec_motion_expect_command(MEC_COPYCHANGE, cmd_forward_char); } /* Appends a space followed by the last bigword from the newest history entry. * If the count is specified, the `count'th word is appended. * The mode is changed to vi-insert. */ void cmd_vi_append_last_bigword(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; save_current_edit_command(); maybe_save_undo_history(); wchar_t *lastcmd = NULL; int count = get_count(-1); if (count == 0 || histlist.count == 0) goto fail; struct xwcsrange range; lastcmd = malloc_mbstowcs(ashistentry(histlist.Newest)->value); if (lastcmd == NULL) goto fail; if (count >= 0) { /* find the count'th word */ range.start = range.end = lastcmd; do { struct xwcsrange r = get_next_bigword(range.end); if (r.start == r.end) break; range = r; } while (--count > 0 && *range.end != L'\0'); } else { /* find the count'th last word */ range.start = range.end = lastcmd + wcslen(lastcmd); do { struct xwcsrange r = get_prev_bigword(lastcmd, range.start); if (r.start == r.end) break; range = r; } while (++count < 0 && lastcmd < range.start); } assert(range.start <= range.end); if (range.start == range.end) goto fail; clear_prediction(); if (le_main_index < le_main_buffer.length) le_main_index++; size_t len = range.end - range.start; wb_ninsert_force(&le_main_buffer, le_main_index, L" ", 1); le_main_index += 1; wb_ninsert_force(&le_main_buffer, le_main_index, range.start, len); le_main_index += len; free(lastcmd); cmd_setmode_viinsert(L'\0'); return; fail: free(lastcmd); cmd_alert(L'\0'); return; } struct xwcsrange get_next_bigword(const wchar_t *s) { struct xwcsrange result; while (iswblank(*s)) s++; result.start = s; while (*s != L'\0' && !iswblank(*s)) s++; result.end = s; return result; /* result.start == result.end if no bigword found */ } struct xwcsrange get_prev_bigword(const wchar_t *beginning, const wchar_t *s) { struct xwcsrange result; assert(beginning <= s); do { if (beginning == s) { result.start = result.end = s; return result; } } while (iswblank(*--s)); result.end = &s[1]; do { if (beginning == s) { result.start = s; return result; } } while (!iswblank(*--s)); result.start = &s[1]; return result; /* result.start == result.end if no bigword found */ } /* Sets the editing mode to "vi expect" and the pending command to * `vi_exec_alias'. */ void cmd_vi_exec_alias(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_char_expect_command(vi_exec_alias); } /* Appends the value of the alias `_c' to the pre-buffer so that the alias value * is interpreted as commands, where `c' in the alias name is the argument of * this command. */ void vi_exec_alias(wchar_t c) { le_set_mode(savemode); state.pending_command_char = 0; wchar_t aliasname[3] = { L'_', c, L'\0', }; const wchar_t *aliasvalue = get_alias_value(aliasname); if (aliasvalue != NULL) { char *mbaliasvalue = malloc_wcstombs(aliasvalue); if (mbaliasvalue != NULL) { le_append_to_prebuffer(mbaliasvalue); return; } } cmd_alert(L'\0'); } /* Invokes an external command to edit the current line and accepts the result. * If the count is set, goes to the `count'th history entry and edit it. * If the editor returns a non-zero status, the line is not accepted. */ /* cf. history.c:fc_edit_and_exec_entries */ void cmd_vi_edit_and_accept(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; char *tempfile; int fd; FILE *f; pid_t cpid; int savelaststatus; if (state.count.sign != 0) { int num = get_count(0); if (num < 0) goto error0; const histlink_T *l = get_history_entry((unsigned) num); if (l == Histlist) goto error0; go_to_history(l, SEARCH_VI); } clear_prediction(); le_complete_cleanup(); le_suspend_readline(); fd = create_temporary_file(&tempfile, ".sh", S_IRUSR | S_IWUSR); if (fd < 0) { xerror(errno, Ngt("cannot create a temporary file to edit history")); goto error1; } f = fdopen(fd, "w"); if (f == NULL) { xerror(errno, Ngt("cannot open temporary file `%s'"), tempfile); xclose(fd); goto error2; } savelaststatus = laststatus; cpid = fork_and_reset(0, true, 0); if (cpid < 0) { // fork failed xerror(0, Ngt("cannot invoke the editor to edit history")); fclose(f); if (unlink(tempfile) < 0) xerror(errno, Ngt("failed to remove temporary file `%s'"), tempfile); error2: free(tempfile); error1: le_resume_readline(); error0: cmd_alert(L'\0'); } else if (cpid > 0) { // parent process fclose(f); wchar_t **namep = wait_for_child(cpid, doing_job_control_now ? cpid : 0, doing_job_control_now); if (namep) *namep = malloc_wprintf(L"vi %s", tempfile); if (laststatus != Exit_SUCCESS) goto end; f = fopen(tempfile, "r"); if (f == NULL) { cmd_alert(L'\0'); } else { wint_t c; wb_clear(&le_main_buffer); while ((c = fgetwc(f)) != WEOF) wb_wccat(&le_main_buffer, (wchar_t) c); fclose(f); /* remove trailing newline */ while (le_main_buffer.length > 0 && le_main_buffer.contents[le_main_buffer.length - 1] == L'\n') wb_remove(&le_main_buffer, le_main_buffer.length - 1, 1); le_main_index = le_main_buffer.length; le_editstate = LE_EDITSTATE_DONE; end: reset_state(); } laststatus = savelaststatus; unlink(tempfile); free(tempfile); if (shopt_notify || shopt_notifyle) print_job_status_all(); le_resume_readline(); } else { // child process fwprintf(f, L"%ls\n", le_main_buffer.contents); fclose(f); wchar_t *command = malloc_wprintf(L"vi %s", tempfile); free(tempfile); exec_wcs(command, gt("lineedit"), true); #ifndef NDEBUG free(command); #endif assert(false); } } /* Performs command line completion and * * if the count is not set, list all the candidates without changing the * main buffer. * * if the count is set, complete the `count'th candidate and set the mode * to vi-insert. */ void cmd_vi_complete_list(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; maybe_save_undo_history(); clear_prediction(); le_complete_cleanup(); /* leave `next_reset_completion' to be true because the results of this * command cannot be used by succeeding completion commands. */ // next_reset_completion = false; size_t oldindex = le_main_index; if (le_main_index < le_main_buffer.length) le_main_index++; if (le_complete_fix_candidate(get_count(0))) { cmd_setmode_viinsert(L'\0'); } else { le_main_index = oldindex; reset_state(); } } /* Performs command line completion and replaces the current word with all of * the generated candidates. * The mode is changed to vi-insert. */ void cmd_vi_complete_all(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); if (le_main_index < le_main_buffer.length) le_main_index++; le_complete(lecr_substitute_all_candidates); cmd_setmode_viinsert(L'\0'); } /* Performs command line completion and replaces the current word with the * longest common prefix of the candidates. * The mode is changed to vi-insert. */ void cmd_vi_complete_max(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; clear_prediction(); check_reset_completion(); if (le_main_index < le_main_buffer.length) le_main_index++; le_complete(lecr_longest_common_prefix); cmd_setmode_viinsert(L'\0'); } /* Starts vi-like command history search in the forward direction. */ void cmd_vi_search_forward(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_search_mode(LE_MODE_VI_SEARCH, FORWARD); } /* Starts vi-like command history search in the backward direction. */ void cmd_vi_search_backward(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_search_mode(LE_MODE_VI_SEARCH, BACKWARD); } /********** Emacs-Mode Specific Commands **********/ /* Moves the character before the cursor to the right by `count' characters. */ void cmd_emacs_transpose_chars(wchar_t c __attribute__((unused))) { //ALERT_AND_RETURN_IF_PENDING; if (state.pending_command_motion != MEC_MOVE || le_main_index == 0) goto error; maybe_save_undo_history(); if (state.count.sign == 0 && le_main_index == le_main_buffer.length && le_main_index >= 2) le_main_index--; int count = get_count(1); size_t index; if (count >= 0) { #if COUNT_ABS_MAX > SIZE_MAX if (count <= (int) (le_main_buffer.length - le_main_index)) #else if ((size_t) count <= le_main_buffer.length - le_main_index) #endif index = le_main_index + (size_t) count; else { le_main_index = le_main_buffer.length; goto error; } } else { #if COUNT_ABS_MAX > SIZE_MAX if (-count < (int) le_main_index) #else if ((size_t) -count < le_main_index) #endif index = le_main_index + (size_t) count; else { le_main_index = 0; goto error; } } size_t old_index = le_main_index; assert(le_main_index > 0); assert(0 < index && index <= le_main_buffer.length); c = le_main_buffer.contents[old_index - 1]; wb_remove(&le_main_buffer, old_index - 1, 1); wb_ninsert(&le_main_buffer, index - 1, &c, 1); le_main_index = index; if (le_main_length < old_index) le_main_length = old_index; if (le_main_length < index) le_main_length = index; reset_state(); return; error: cmd_alert(L'\0'); } /* Interchanges the word before the cursor and the `count' words after the * cursor. */ void cmd_emacs_transpose_words(wchar_t c __attribute__((unused))) { if (state.pending_command_motion != MEC_MOVE || le_main_index == 0) goto error; maybe_save_undo_history(); int count = get_count(1); size_t w1start, w1end, w2start, w2end, new_index; xwcsbuf_T buf; if (count == 0) goto end; w1start = previous_emacsword_index(le_main_buffer.contents, le_main_index); w1end = next_emacsword_index(le_main_buffer.contents, w1start); if (count >= 0) { w2end = next_emacsword_index(le_main_buffer.contents, w1end); w2start = previous_emacsword_index(le_main_buffer.contents, w2end); while (--count > 0) { if (w2end == le_main_buffer.length) goto error; w2end = next_emacsword_index(le_main_buffer.contents, w2end); } new_index = w2end; } else { w2start = w1start, w2end = w1end; w1start = previous_emacsword_index(le_main_buffer.contents, w2start); w1end = next_emacsword_index(le_main_buffer.contents, w1start); while (++count < 0) { if (w1start == 0) goto error; w1start = previous_emacsword_index( le_main_buffer.contents, w1start); } new_index = w1start + (w2end - w2start); } if (w1end >= w2start) goto error; wb_init(&buf); wb_ncat_force(&buf, &le_main_buffer.contents[w2start], w2end - w2start); wb_ncat_force(&buf, &le_main_buffer.contents[w1end], w2start - w1end); wb_ncat_force(&buf, &le_main_buffer.contents[w1start], w1end - w1start); assert(buf.length == w2end - w1start); wb_replace_force(&le_main_buffer, w1start, buf.length, buf.contents, buf.length); wb_destroy(&buf); le_main_index = new_index; if (le_main_length < w2end) le_main_length = w2end; end: reset_state(); return; error: cmd_alert(L'\0'); } /* Converts the word after the cursor to lower case. * If the count is set, `count' words are converted. * The cursor is left after the last converted word. */ void cmd_emacs_downcase_word(wchar_t c __attribute__((unused))) { exec_motion_expect_command( MEC_LOWERCASE | MEC_TOEND, cmd_forward_emacsword); } /* Converts `count' words after the cursor to upper case. * If the count is set, `count' words are converted. * The cursor is left after the last converted word. */ void cmd_emacs_upcase_word(wchar_t c __attribute__((unused))) { exec_motion_expect_command( MEC_UPPERCASE | MEC_TOEND, cmd_forward_emacsword); } /* Capitalizes the word after the cursor. * If the count is set, `count' words are capitalized. * The cursor is left after the last capitalized word. */ void cmd_emacs_capitalize_word(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; maybe_save_undo_history(); int count = get_count(1); if (count > 0) { wchar_t *s = &le_main_buffer.contents[le_main_index]; do { while (*s != L'\0' && !iswalnum(*s)) s++; *s = towupper(*s); s++; while (*s != L'\0' && iswalnum(*s)) s++; } while (*s != L'\0' && --count > 0); le_main_index = s - le_main_buffer.contents; } else if (count < 0) { size_t index = le_main_index; do { index = previous_emacsword_index(le_main_buffer.contents, index); le_main_buffer.contents[index] = towupper(le_main_buffer.contents[index]); } while (index > 0 && ++count < 0); } if (le_main_length < le_main_index) le_main_length = le_main_index; reset_state(); } /* Deletes blank characters around the cursor. * If the count is set, only blanks before the cursor are deleted. */ void cmd_emacs_delete_horizontal_space(wchar_t c __attribute__((unused))) { replace_horizontal_space(state.count.sign == 0, L""); } /* Replaces blank characters around the cursor with a space. * The cursor is left after the space. * A space is inserted even if there are no blanks around the cursor. * If the count is specified, blanks are replaced with `count' spaces. */ void cmd_emacs_just_one_space(wchar_t c __attribute__((unused))) { int count = get_count(1); if (count < 0) count = 0; else if (count > 1000) count = 1000; wchar_t s[count + 1]; wmemset(s, L' ', count); s[count] = L'\0'; replace_horizontal_space(true, s); } /* Replaces blank characters around the cursor with the specified string. * If `deleteafter' is true, blanks after the cursor are replaced as well as * blanks before the cursor. If `deleteafter' is false, only blanks before the * cursor are replaced. * The cursor is left after the replacement. */ void replace_horizontal_space(bool deleteafter, const wchar_t *s) { ALERT_AND_RETURN_IF_PENDING; maybe_save_undo_history(); size_t start_index = le_main_index; while (start_index > 0 && iswblank(le_main_buffer.contents[start_index - 1])) start_index--; size_t end_index = le_main_index; if (deleteafter) while (end_index < le_main_buffer.length && iswblank(le_main_buffer.contents[end_index])) end_index++; if (le_main_length < end_index) le_main_length = end_index; size_t slen = wcslen(s); wb_replace_force(&le_main_buffer, start_index, end_index - start_index, s, slen); le_main_index = start_index + slen; le_main_length += le_main_index - end_index; reset_state(); } /* Starts emacs-like command history search in the forward direction. */ void cmd_emacs_search_forward(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_search_mode(LE_MODE_EMACS_SEARCH, FORWARD); } /* Starts emacs-like command history search in the backward direction. */ void cmd_emacs_search_backward(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; set_search_mode(LE_MODE_EMACS_SEARCH, BACKWARD); } /********** History-Related Commands **********/ /* Goes to the oldest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor position is not changed. */ void cmd_oldest_history(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Oldest, SEARCH_PREFIX); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor position is not changed. */ void cmd_newest_history(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Newest, SEARCH_PREFIX); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor position is not changed. */ void cmd_return_history(wchar_t c __attribute__((unused))) { go_to_history_absolute(Histlist, SEARCH_PREFIX); } /* Goes to the oldest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the beginning of line. */ void cmd_oldest_history_bol(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Oldest, SEARCH_VI); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the beginning of line. */ void cmd_newest_history_bol(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Newest, SEARCH_VI); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the beginning of line. */ void cmd_return_history_bol(wchar_t c __attribute__((unused))) { go_to_history_absolute(Histlist, SEARCH_VI); } /* Goes to the oldest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the end of line. */ void cmd_oldest_history_eol(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Oldest, SEARCH_EMACS); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the end of line. */ void cmd_newest_history_eol(wchar_t c __attribute__((unused))) { go_to_history_absolute(histlist.Newest, SEARCH_EMACS); } /* Goes to the newest history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * The cursor is put at the end of line. */ void cmd_return_history_eol(wchar_t c __attribute__((unused))) { go_to_history_absolute(Histlist, SEARCH_EMACS); } /* Goes to the specified history entry. * If the count is specified, goes to the history entry whose number is count. * If the specified entry is not found, the terminal is alerted. * See `go_to_history' for the meaning of `curpos'. */ void go_to_history_absolute(const histlink_T *l, enum le_search_type_T curpos) { ALERT_AND_RETURN_IF_PENDING; if (state.count.sign == 0) { if (histlist.count == 0) goto alert; } else { int num = get_count(0); if (num <= 0) goto alert; l = get_history_entry((unsigned) num); if (l == Histlist) goto alert; } go_to_history(l, curpos); reset_state(); return; alert: cmd_alert(L'\0'); return; } /* Goes to the `count'th next history entry. * The cursor position is not changed. */ void cmd_next_history(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(get_count(1), SEARCH_PREFIX); } /* Goes to the `count'th previous history entry. * The cursor position is not changed. */ void cmd_prev_history(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(-get_count(1), SEARCH_PREFIX); } /* Goes to the `count'th next history entry. * The cursor is put at the beginning of line. */ void cmd_next_history_bol(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(get_count(1), SEARCH_VI); } /* Goes to the `count'th previous history entry. * The cursor is put at the beginning of line. */ void cmd_prev_history_bol(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(-get_count(1), SEARCH_VI); } /* Goes to the `count'th next history entry. * The cursor is put at the end of line. */ void cmd_next_history_eol(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(get_count(1), SEARCH_EMACS); } /* Goes to the `count'th previous history entry. * The cursor is put at the end of line. */ void cmd_prev_history_eol(wchar_t c __attribute__((unused))) { ALERT_AND_RETURN_IF_PENDING; go_to_history_relative(-get_count(1), SEARCH_EMACS); } /* Goes to the `offset'th next history entry. * See `go_to_history' for the meaning of `curpos'. */ void go_to_history_relative(int offset, enum le_search_type_T curpos) { const histlink_T *l = main_history_entry; if (offset > 0) { do { if (l == Histlist) goto alert; l = l->next; } while (--offset > 0); } else if (offset < 0) { do { l = l->prev; if (l == Histlist) goto alert; } while (++offset < 0); } go_to_history(l, curpos); reset_state(); return; alert: cmd_alert(L'\0'); return; } /* Sets the value of the specified history entry to the main buffer. * The value of `curpos' specifies where the cursor is left: * SEARCH_PREFIX: the current position (unless it exceeds the buffer length) * SEARCH_VI: the beginning of the buffer * SEARCH_EMACS: the end of the buffer */ void go_to_history(const histlink_T *l, enum le_search_type_T curpos) { maybe_save_undo_history(); clear_prediction(); free(main_history_value); wb_clear(&le_main_buffer); if (l == undo_history_entry && undo_index < undo_history.length) { struct undo_history *h = undo_history.contents[undo_index]; wb_cat(&le_main_buffer, h->contents); assert(h->index <= le_main_buffer.length); le_main_index = h->index; } else { if (l != Histlist) wb_mbscat(&le_main_buffer, ashistentry(l)->value); switch (curpos) { case SEARCH_PREFIX: if (le_main_index > le_main_buffer.length) le_main_index = le_main_buffer.length; break; case SEARCH_VI: le_main_index = 0; break; case SEARCH_EMACS: le_main_index = le_main_buffer.length; break; } } main_history_entry = l; main_history_value = xwcsdup(le_main_buffer.contents); undo_save_index = le_main_index; } /***** History Search Commands *****/ /* Appends the argument character to the search buffer. */ void cmd_srch_self_insert(wchar_t c) { if (le_search_buffer.contents == NULL || c == L'\0') { cmd_alert(L'\0'); return; } wb_wccat(&le_search_buffer, c); update_search(); } /* Removes the last character from the search buffer. * If there are no characters in the buffer, calls `cmd_srch_abort_search' (for * vi-like search) or `cmd_alert' (for emacs-like search). */ void cmd_srch_backward_delete_char(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) { cmd_alert(L'\0'); return; } if (le_search_buffer.length == 0) { switch (le_search_type) { case SEARCH_VI: cmd_srch_abort_search(L'\0'); return; case SEARCH_EMACS: cmd_alert(L'\0'); return; case SEARCH_PREFIX: assert(false); } } wb_remove(&le_search_buffer, le_search_buffer.length - 1, 1); update_search(); } /* Removes all characters from the search buffer. */ void cmd_srch_backward_delete_line(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) { cmd_alert(L'\0'); return; } wb_clear(&le_search_buffer); update_search(); } /* Settles the current search result and continues the search with the current * pattern in the forward direction. This is like `cmd_search_again_forward' * but this command can be used during the search. */ void cmd_srch_continue_forward(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) { cmd_alert(L'\0'); return; } le_search_direction = FORWARD; if (le_search_result != Histlist) go_to_history(le_search_result, le_search_type); update_search(); } /* Settles the current search result and continues the search with the current * pattern in the backward direction. This is like `cmd_search_again_backward' * but this command can be used during the search. */ void cmd_srch_continue_backward(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) { cmd_alert(L'\0'); return; } le_search_direction = BACKWARD; if (le_search_result != Histlist) go_to_history(le_search_result, le_search_type); update_search(); } /* Finishes the history search and accepts the current result candidate. * If no search is being performed, does nothing. */ void cmd_srch_accept_search(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) return; last_search.direction = le_search_direction; last_search.type = le_search_type; if (need_update_last_search_value()) { free(last_search.value); last_search.value = wb_towcs(&le_search_buffer); } else { wb_destroy(&le_search_buffer); } le_search_buffer.contents = NULL; le_set_mode(savemode); if (le_search_result == Histlist) { cmd_alert(L'\0'); } else { go_to_history(le_search_result, le_search_type); } reset_state(); } /* Checks if we should update `last_search.value' to the current value of * `le_search_buffer'. */ bool need_update_last_search_value(void) { switch (le_search_type) { case SEARCH_PREFIX: break; case SEARCH_VI: if (le_search_buffer.contents[0] == L'\0') return false; if (le_search_buffer.contents[0] == L'^' && le_search_buffer.contents[1] == L'\0') return false; return true; case SEARCH_EMACS: return le_search_buffer.contents[0] != L'\0'; } assert(false); } /* Aborts the history search. * If no search is being performed, does nothing. */ void cmd_srch_abort_search(wchar_t c __attribute__((unused))) { if (le_search_buffer.contents == NULL) return; wb_destroy(&le_search_buffer); le_search_buffer.contents = NULL; le_set_mode(savemode); reset_state(); } /* Re-calculates the search result candidate. */ void update_search(void) { const wchar_t *pattern = le_search_buffer.contents; if (pattern[0] == L'\0') { switch (le_search_type) { case SEARCH_PREFIX: break; case SEARCH_VI: pattern = last_search.value; if (pattern == NULL) { le_search_result = Histlist; goto done; } break; case SEARCH_EMACS: le_search_result = Histlist; goto done; } } perform_search(pattern, le_search_direction, le_search_type); done: reset_state(); } /* Performs history search with the given parameters and updates the result * candidate. */ void perform_search(const wchar_t *pattern, enum le_search_direction_T dir, enum le_search_type_T type) { const histlink_T *l = main_history_entry; xfnmatch_T *xfnm; if (dir == FORWARD && l == Histlist) goto done; switch (type) { case SEARCH_PREFIX: { wchar_t *p = escape(pattern, NULL); xfnm = xfnm_compile(p, XFNM_HEADONLY); free(p); break; } case SEARCH_VI: { xfnmflags_T flags = 0; if (pattern[0] == L'^') { flags |= XFNM_HEADONLY; pattern++; if (pattern[0] == L'\0') { l = Histlist; goto done; } } xfnm = xfnm_compile(pattern, flags); break; } case SEARCH_EMACS: { wchar_t *p = escape(pattern, NULL); xfnm = xfnm_compile(p, 0); free(p); break; } default: assert(false); } if (xfnm == NULL) { l = Histlist; goto done; } for (;;) { switch (dir) { case FORWARD: l = l->next; break; case BACKWARD: l = l->prev; break; } if (l == Histlist) break; if (xfnm_match(xfnm, ashistentry(l)->value) == 0) break; } xfnm_free(xfnm); done: le_search_result = l; } /* Redoes the last search. */ void cmd_search_again(wchar_t c __attribute__((unused))) { search_again(last_search.direction); } /* Redoes the last search in the reverse direction. */ void cmd_search_again_rev(wchar_t c __attribute__((unused))) { switch (last_search.direction) { case FORWARD: search_again(BACKWARD); break; case BACKWARD: search_again(FORWARD); break; } } /* Redoes the last search in the forward direction. */ void cmd_search_again_forward(wchar_t c __attribute__((unused))) { search_again(FORWARD); } /* Redoes the last search in the backward direction. */ void cmd_search_again_backward(wchar_t c __attribute__((unused))) { search_again(BACKWARD); } /* Performs command search for the last search pattern in the specified * direction. If the count is set, re-search `count' times. If the count is * negative, search in the opposite direction. */ void search_again(enum le_search_direction_T dir) { ALERT_AND_RETURN_IF_PENDING; if (last_search.value == NULL) { cmd_alert(L'\0'); return; } int count = get_count(1); if (count < 0) { count = -count; switch (dir) { case FORWARD: dir = BACKWARD; break; case BACKWARD: dir = FORWARD; break; } } while (--count >= 0) { perform_search(last_search.value, dir, last_search.type); if (le_search_result == Histlist) { cmd_alert(L'\0'); break; } else { go_to_history(le_search_result, last_search.type); } } reset_state(); } /* Searches the history in the forward direction for an entry that has a common * prefix with the current buffer contents. The "prefix" is the first * `le_main_index' characters of the buffer. */ void cmd_beginning_search_forward(wchar_t c __attribute__((unused))) { beginning_search(FORWARD); } /* Searches the history in the backward direction for an entry that has a common * prefix with the current buffer contents. The "prefix" is the first * `le_main_index' characters of the buffer. */ void cmd_beginning_search_backward(wchar_t c __attribute__((unused))) { beginning_search(BACKWARD); } /* Searches the history in the specified direction for an entry that has a * common prefix with the current buffer contents. The "prefix" is the first * `le_main_index' characters of the buffer. */ void beginning_search(enum le_search_direction_T dir) { ALERT_AND_RETURN_IF_PENDING; int count = get_count(1); if (count < 0) { count = -count; switch (dir) { case FORWARD: dir = BACKWARD; break; case BACKWARD: dir = FORWARD; break; } } wchar_t *prefix = xwcsndup(le_main_buffer.contents, le_main_index); while (--count >= 0) { perform_search(prefix, dir, SEARCH_PREFIX); if (le_search_result == Histlist) { if (dir == FORWARD && beginning_search_check_go_to_history(prefix)) go_to_history(le_search_result, SEARCH_PREFIX); else cmd_alert(L'\0'); break; } else { go_to_history(le_search_result, SEARCH_PREFIX); } } free(prefix); reset_state(); } bool beginning_search_check_go_to_history(const wchar_t *prefix) { if (le_search_result == undo_history_entry && undo_index < undo_history.length) { const struct undo_history *h = undo_history.contents[undo_index]; return matchwcsprefix(h->contents, prefix) != NULL; } else { return false; } } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/editing.h000066400000000000000000000162131354143602500160130ustar00rootroot00000000000000/* Yash: yet another shell */ /* editing.h: main editing module */ /* (C) 2007-2017 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_EDITING_H #define YASH_EDITING_H #include #include "../strbuf.h" #include "key.h" extern xwcsbuf_T le_main_buffer; extern size_t le_main_length, le_main_index; enum le_search_direction_T { FORWARD, BACKWARD, }; /* FORWARD: find the oldest candidate from the ones newer than the current * BACKWARD: find the newest candidate from the ones older than the current */ enum le_search_type_T { SEARCH_PREFIX, SEARCH_VI, SEARCH_EMACS, }; extern enum le_search_direction_T le_search_direction; extern enum le_search_type_T le_search_type; extern xwcsbuf_T le_search_buffer; extern const struct histlink_T *le_search_result; extern void le_editing_init(void); extern wchar_t *le_editing_finalize(void) __attribute__((malloc,warn_unused_result)); extern void le_invoke_command(le_command_func_T *cmd, wchar_t arg) __attribute__((nonnull)); /********** Commands **********/ // This header file is parsed by the Makefile script to create the "commands.in" // file. For every line that ends with "/*C*/", the first word that starts with // "cmd_" is interpreted as a command name. /* basic commands */ extern le_command_func_T cmd_noop, /*C*/ cmd_alert, /*C*/ cmd_self_insert, /*C*/ cmd_insert_tab, /*C*/ cmd_expect_verbatim, /*C*/ cmd_digit_argument, /*C*/ cmd_bol_or_digit, /*C*/ cmd_accept_line, /*C*/ cmd_abort_line, /*C*/ cmd_eof, /*C*/ cmd_eof_if_empty, /*C*/ cmd_eof_or_delete, /*C*/ cmd_accept_with_hash, /*C*/ cmd_accept_prediction, /*C*/ cmd_setmode_viinsert, /*C*/ cmd_setmode_vicommand, /*C*/ cmd_setmode_emacs, /*C*/ cmd_expect_char, /*C*/ cmd_abort_expect_char, /*C*/ cmd_redraw_all, /*C*/ cmd_clear_and_redraw_all; /*C*/ /* motion commands */ extern le_command_func_T cmd_forward_char, /*C*/ cmd_backward_char, /*C*/ cmd_forward_bigword, /*C*/ cmd_end_of_bigword, /*C*/ cmd_backward_bigword, /*C*/ cmd_forward_semiword, /*C*/ cmd_end_of_semiword, /*C*/ cmd_backward_semiword, /*C*/ cmd_forward_viword, /*C*/ cmd_end_of_viword, /*C*/ cmd_backward_viword, /*C*/ cmd_forward_emacsword, /*C*/ cmd_backward_emacsword, /*C*/ cmd_beginning_of_line, /*C*/ cmd_end_of_line, /*C*/ cmd_go_to_column, /*C*/ cmd_first_nonblank, /*C*/ cmd_find_char, /*C*/ cmd_find_char_rev, /*C*/ cmd_till_char, /*C*/ cmd_till_char_rev, /*C*/ cmd_refind_char, /*C*/ cmd_refind_char_rev; /*C*/ /* editing commands */ extern le_command_func_T cmd_delete_char, /*C*/ cmd_delete_bigword, /*C*/ cmd_delete_semiword, /*C*/ cmd_delete_viword, /*C*/ cmd_delete_emacsword, /*C*/ cmd_backward_delete_char, /*C*/ cmd_backward_delete_bigword, /*C*/ cmd_backward_delete_semiword, /*C*/ cmd_backward_delete_viword, /*C*/ cmd_backward_delete_emacsword, /*C*/ cmd_delete_line, /*C*/ cmd_forward_delete_line, /*C*/ cmd_backward_delete_line, /*C*/ cmd_kill_char, /*C*/ cmd_kill_bigword, /*C*/ cmd_kill_semiword, /*C*/ cmd_kill_viword, /*C*/ cmd_kill_emacsword, /*C*/ cmd_backward_kill_char, /*C*/ cmd_backward_kill_bigword, /*C*/ cmd_backward_kill_semiword, /*C*/ cmd_backward_kill_viword, /*C*/ cmd_backward_kill_emacsword, /*C*/ cmd_kill_line, /*C*/ cmd_forward_kill_line, /*C*/ cmd_backward_kill_line, /*C*/ cmd_put_before, /*C*/ cmd_put, /*C*/ cmd_put_left, /*C*/ cmd_put_pop, /*C*/ cmd_undo, /*C*/ cmd_undo_all, /*C*/ cmd_cancel_undo, /*C*/ cmd_cancel_undo_all, /*C*/ cmd_redo; /*C*/ /* completion commands */ extern le_command_func_T cmd_complete, /*C*/ cmd_complete_next_candidate, /*C*/ cmd_complete_prev_candidate, /*C*/ cmd_complete_next_column, /*C*/ cmd_complete_prev_column, /*C*/ cmd_complete_next_page, /*C*/ cmd_complete_prev_page, /*C*/ cmd_complete_list, /*C*/ cmd_complete_all, /*C*/ cmd_complete_max, /*C*/ cmd_complete_max_then_list, /*C*/ cmd_complete_max_then_next_candidate, /*C*/ cmd_complete_max_then_prev_candidate, /*C*/ cmd_clear_candidates; /*C*/ /* vi-mode specific commands */ extern le_command_func_T cmd_vi_replace_char, /*C*/ cmd_vi_insert_beginning, /*C*/ cmd_vi_append, /*C*/ cmd_vi_append_to_eol, /*C*/ cmd_vi_replace, /*C*/ cmd_vi_switch_case, /*C*/ cmd_vi_switch_case_char, /*C*/ cmd_vi_yank, /*C*/ cmd_vi_yank_to_eol, /*C*/ cmd_vi_delete, /*C*/ #define cmd_vi_delete_to_eol cmd_forward_kill_line /*C*/ cmd_vi_change, /*C*/ cmd_vi_change_to_eol, /*C*/ cmd_vi_change_line, /*C*/ cmd_vi_yank_and_change, /*C*/ cmd_vi_yank_and_change_to_eol, /*C*/ cmd_vi_yank_and_change_line, /*C*/ cmd_vi_substitute, /*C*/ cmd_vi_append_last_bigword, /*C*/ cmd_vi_exec_alias, /*C*/ cmd_vi_edit_and_accept, /*C*/ cmd_vi_complete_list, /*C*/ cmd_vi_complete_all, /*C*/ cmd_vi_complete_max, /*C*/ cmd_vi_search_forward, /*C*/ cmd_vi_search_backward; /*C*/ /* emacs-mode specific commands */ extern le_command_func_T cmd_emacs_transpose_chars, /*C*/ cmd_emacs_transpose_words, /*C*/ cmd_emacs_upcase_word, /*C*/ cmd_emacs_downcase_word, /*C*/ cmd_emacs_capitalize_word, /*C*/ cmd_emacs_delete_horizontal_space, /*C*/ cmd_emacs_just_one_space, /*C*/ cmd_emacs_search_forward, /*C*/ cmd_emacs_search_backward; /*C*/ /* history-related commands */ extern le_command_func_T cmd_oldest_history, /*C*/ cmd_newest_history, /*C*/ cmd_return_history, /*C*/ cmd_oldest_history_bol, /*C*/ cmd_newest_history_bol, /*C*/ cmd_return_history_bol, /*C*/ cmd_oldest_history_eol, /*C*/ cmd_newest_history_eol, /*C*/ cmd_return_history_eol, /*C*/ cmd_next_history, /*C*/ cmd_prev_history, /*C*/ cmd_next_history_bol, /*C*/ cmd_prev_history_bol, /*C*/ cmd_next_history_eol, /*C*/ cmd_prev_history_eol, /*C*/ /* command history search */ cmd_srch_self_insert, /*C*/ cmd_srch_backward_delete_char, /*C*/ cmd_srch_backward_delete_line, /*C*/ cmd_srch_continue_forward, /*C*/ cmd_srch_continue_backward, /*C*/ cmd_srch_accept_search, /*C*/ cmd_srch_abort_search, /*C*/ cmd_search_again, /*C*/ cmd_search_again_rev, /*C*/ cmd_search_again_forward, /*C*/ cmd_search_again_backward, /*C*/ cmd_beginning_search_forward, /*C*/ cmd_beginning_search_backward; /*C*/ #endif /* YASH_EDITING_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/key.h000066400000000000000000000241701354143602500151610ustar00rootroot00000000000000/* Yash: yet another shell */ /* key.h: definition of key codes */ /* (C) 2007-2009 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_KEY_H #define YASH_KEY_H #include /* Strings representing special keys */ #define Key_a1 L"\\a1" // upper left of keypad #define Key_a3 L"\\a3" // upper right of keypad #define Key_b2 L"\\b2" // center of keypad #define Key_backspace L"\\B" // backspace #define Key_beg L"\\bg" // beg #define Key_btab L"\\bt" // back-tab #define Key_c1 L"\\c1" // lower left of keypad #define Key_c3 L"\\c3" // lower right of keypad #define Key_cancel L"\\cn" // cancel key #define Key_catab L"\\ca" // clear-all-tabs key #define Key_clear L"\\cs" // clear-screen or erase key #define Key_close L"\\cl" // close key #define Key_command L"\\co" // command (cmd) key #define Key_copy L"\\cp" // copy key #define Key_create L"\\cr" // create key #define Key_ctab L"\\ct" // clear-tab key #define Key_delete L"\\X" // delete (delete-character) key #define Key_dl L"\\dl" // delete-line key #define Key_down L"\\D" // down-arrow key #define Key_eic L"\\ei" // exit-insert-mode #define Key_end L"\\E" // end key #define Key_enter L"\\et" // enter/send key #define Key_eol L"\\el" // clear-to-end-of-line key #define Key_eos L"\\es" // clear-to-end-of-screen key #define Key_exit L"\\ex" // exit key #define Key_f00 L"\\F00" // function key F0 #define Key_f01 L"\\F01" // function key F1 #define Key_f02 L"\\F02" // function key F2 #define Key_f03 L"\\F03" // function key F3 #define Key_f04 L"\\F04" // function key F4 #define Key_f05 L"\\F05" // function key F5 #define Key_f06 L"\\F06" // function key F6 #define Key_f07 L"\\F07" // function key F7 #define Key_f08 L"\\F08" // function key F8 #define Key_f09 L"\\F09" // function key F9 #define Key_f10 L"\\F10" // function key F10 #define Key_f11 L"\\F11" // function key F11 #define Key_f12 L"\\F12" // function key F12 #define Key_f13 L"\\F13" // function key F13 #define Key_f14 L"\\F14" // function key F14 #define Key_f15 L"\\F15" // function key F15 #define Key_f16 L"\\F16" // function key F16 #define Key_f17 L"\\F17" // function key F17 #define Key_f18 L"\\F18" // function key F18 #define Key_f19 L"\\F19" // function key F19 #define Key_f20 L"\\F20" // function key F20 #define Key_f21 L"\\F21" // function key F21 #define Key_f22 L"\\F22" // function key F22 #define Key_f23 L"\\F23" // function key F23 #define Key_f24 L"\\F24" // function key F24 #define Key_f25 L"\\F25" // function key F25 #define Key_f26 L"\\F26" // function key F26 #define Key_f27 L"\\F27" // function key F27 #define Key_f28 L"\\F28" // function key F28 #define Key_f29 L"\\F29" // function key F29 #define Key_f30 L"\\F30" // function key F30 #define Key_f31 L"\\F31" // function key F31 #define Key_f32 L"\\F32" // function key F32 #define Key_f33 L"\\F33" // function key F33 #define Key_f34 L"\\F34" // function key F34 #define Key_f35 L"\\F35" // function key F35 #define Key_f36 L"\\F36" // function key F36 #define Key_f37 L"\\F37" // function key F37 #define Key_f38 L"\\F38" // function key F38 #define Key_f39 L"\\F39" // function key F39 #define Key_f40 L"\\F40" // function key F40 #define Key_f41 L"\\F41" // function key F41 #define Key_f42 L"\\F42" // function key F42 #define Key_f43 L"\\F43" // function key F43 #define Key_f44 L"\\F44" // function key F44 #define Key_f45 L"\\F45" // function key F45 #define Key_f46 L"\\F46" // function key F46 #define Key_f47 L"\\F47" // function key F47 #define Key_f48 L"\\F48" // function key F48 #define Key_f49 L"\\F49" // function key F49 #define Key_f50 L"\\F50" // function key F50 #define Key_f51 L"\\F51" // function key F51 #define Key_f52 L"\\F52" // function key F52 #define Key_f53 L"\\F53" // function key F53 #define Key_f54 L"\\F54" // function key F54 #define Key_f55 L"\\F55" // function key F55 #define Key_f56 L"\\F56" // function key F56 #define Key_f57 L"\\F57" // function key F57 #define Key_f58 L"\\F58" // function key F58 #define Key_f59 L"\\F59" // function key F59 #define Key_f60 L"\\F60" // function key F60 #define Key_f61 L"\\F61" // function key F61 #define Key_f62 L"\\F62" // function key F62 #define Key_f63 L"\\F63" // function key F63 #define Key_find L"\\fd" // find key #define Key_help L"\\hp" // help key #define Key_home L"\\H" // home key #define Key_insert L"\\I" // insert (insert-char/enter-insert-mode) #define Key_il L"\\il" // insert-line key #define Key_left L"\\L" // left-arrow key #define Key_ll L"\\ll" // home-down key #define Key_mark L"\\mk" // mark key #define Key_message L"\\me" // message key #define Key_mouse L"\\ms" // mouse event #define Key_move L"\\mv" // move key #define Key_next L"\\nx" // next-object key #define Key_pagedown L"\\N" // page-down (next-page) key #define Key_open L"\\on" // open key #define Key_options L"\\op" // options key #define Key_pageup L"\\P" // page-up (previous-page) key #define Key_previous L"\\pv" // previous-object key #define Key_print L"\\pr" // print/copy key #define Key_redo L"\\rd" // redo key #define Key_reference L"\\rf" // ref(erence) key #define Key_refresh L"\\rh" // refresh key #define Key_replace L"\\rp" // replace key #define Key_restart L"\\rs" // restart key #define Key_resume L"\\re" // resume key #define Key_right L"\\R" // right-arrow key #define Key_save L"\\sv" // save key #define Key_select L"\\sl" // select key #define Key_sf L"\\sf" // scroll-forward key #define Key_sr L"\\sr" // scroll-backward key #define Key_stab L"\\st" // set-tab key #define Key_suspend L"\\su" // suspend key #define Key_undo L"\\ud" // undo key #define Key_up L"\\U" // up-arrow key #define Key_s_beg L"\\Sbg" // shift+beginning #define Key_s_cancel L"\\Scn" // shift+cancel #define Key_s_command L"\\Sco" // shift+command #define Key_s_copy L"\\Scp" // shift+copy #define Key_s_create L"\\Scr" // shift+create #define Key_s_delete L"\\SX" // shift+delete #define Key_s_dl L"\\Sdl" // shift+delete-line #define Key_s_end L"\\SE" // shift+end #define Key_s_eol L"\\Sel" // shift+end-of-line #define Key_s_exit L"\\Sex" // shift+exit #define Key_s_find L"\\Sfd" // shift+find #define Key_s_help L"\\Shp" // shift+help #define Key_s_home L"\\SH" // shift+home #define Key_s_insert L"\\SI" // shift+insert #define Key_s_left L"\\SL" // shift+left #define Key_s_message L"\\Smg" // shift+message #define Key_s_move L"\\Smv" // shift+move #define Key_s_next L"\\Snx" // shift+next #define Key_s_options L"\\Sop" // shift+options #define Key_s_prev L"\\Spv" // shift+previous #define Key_s_print L"\\Spr" // shift+print #define Key_s_redo L"\\Srd" // shift+redo #define Key_s_replace L"\\Srp" // shift+replace #define Key_s_right L"\\SR" // shift+right #define Key_s_resume L"\\Sre" // shift+resume #define Key_s_save L"\\Ssv" // shift+save #define Key_s_suspend L"\\Ssu" // shift+suspend #define Key_s_undo L"\\Sud" // shift+undo #define Key_c_at L"\\^@" // ctrl+@ #define Key_c_a L"\\^A" // ctrl+A #define Key_c_b L"\\^B" // ctrl+B #define Key_c_c L"\\^C" // ctrl+C #define Key_c_d L"\\^D" // ctrl+D #define Key_c_e L"\\^E" // ctrl+E #define Key_c_f L"\\^F" // ctrl+F #define Key_c_g L"\\^G" // ctrl+G #define Key_c_h L"\\^H" // ctrl+H #define Key_c_i L"\\^I" // ctrl+I #define Key_c_j L"\\^J" // ctrl+J #define Key_c_k L"\\^K" // ctrl+K #define Key_c_l L"\\^L" // ctrl+L #define Key_c_m L"\\^M" // ctrl+M #define Key_c_n L"\\^N" // ctrl+N #define Key_c_o L"\\^O" // ctrl+O #define Key_c_p L"\\^P" // ctrl+P #define Key_c_q L"\\^Q" // ctrl+Q #define Key_c_r L"\\^R" // ctrl+R #define Key_c_s L"\\^S" // ctrl+S #define Key_c_t L"\\^T" // ctrl+T #define Key_c_u L"\\^U" // ctrl+U #define Key_c_v L"\\^V" // ctrl+V #define Key_c_w L"\\^W" // ctrl+W #define Key_c_x L"\\^X" // ctrl+X #define Key_c_y L"\\^Y" // ctrl+Y #define Key_c_z L"\\^Z" // ctrl+Z #define Key_c_lb L"\\^[" // ctrl+[ #define Key_c_bs L"\\^\\" // ctrl+\ // #define Key_c_rb L"\\^]" // ctrl+] #define Key_c_hat L"\\^^" // ctrl+^ #define Key_c_ul L"\\^_" // ctrl+_ #define Key_c_del L"\\^?" // delete #define Key_interrupt L"\\!" // INTR #define Key_eof L"\\#" // EOF #define Key_kill L"\\$" // KILL #define Key_erase L"\\?" // ERASE #define Key_tab Key_c_i #define Key_newline Key_c_j #define Key_cr Key_c_m #define Key_escape Key_c_lb #define Key_backslash L"\\\\" // must end with '\\' #define META_BIT 0x80 #define ESCAPE_CHAR '\33' /* The type of functions that implement commands. */ typedef void le_command_func_T(wchar_t wc); #endif /* YASH_KEY_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/keymap.c000066400000000000000000000510041354143602500156460ustar00rootroot00000000000000/* Yash: yet another shell */ /* keymap.c: mappings from keys to functions */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "keymap.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include "../builtin.h" #include "../exec.h" #include "../expand.h" #include "../util.h" #include "../xfnmatch.h" #include "complete.h" #include "editing.h" #include "key.h" #include "trie.h" /* Definition of editing modes. */ le_mode_T le_modes[LE_MODE_N]; /* The current editing mode. * Points to one of the modes in `le_modes'. */ le_mode_T *le_current_mode; /* Array of pairs of a command name and function. * Sorted by name. */ static const struct command_name_pair { const char *name; le_command_func_T *command; } commands[] = { #include "commands.in" }; /* Initializes `le_modes' if not yet initialized. * May be called more than once but does nothing if so. */ void le_keymap_init(void) { static bool initialized = false; if (initialized) return; initialized = true; trie_T *t; #define Set(key, cmd) \ (t = trie_setw(t, key, (trievalue_T) { .cmdfunc = (cmd) })) le_modes[LE_MODE_VI_INSERT].default_command = cmd_self_insert; t = trie_create(); Set(Key_backslash, cmd_self_insert); Set(Key_c_v, cmd_expect_verbatim); Set(Key_right, cmd_forward_char); Set(Key_left, cmd_backward_char); Set(Key_home, cmd_beginning_of_line); Set(Key_end, cmd_end_of_line); Set(Key_c_j, cmd_accept_line); Set(Key_c_m, cmd_accept_line); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_eof, cmd_eof_if_empty); Set(Key_c_d, cmd_eof_if_empty); Set(Key_escape, cmd_setmode_vicommand); Set(Key_c_l, cmd_redraw_all); Set(Key_delete, cmd_delete_char); Set(Key_backspace, cmd_backward_delete_char); Set(Key_erase, cmd_backward_delete_char); Set(Key_c_h, cmd_backward_delete_char); Set(Key_c_w, cmd_backward_delete_semiword); Set(Key_kill, cmd_backward_delete_line); Set(Key_c_u, cmd_backward_delete_line); Set(Key_tab, cmd_complete_next_candidate); Set(Key_btab, cmd_complete_prev_candidate); Set(Key_down, cmd_next_history_eol); Set(Key_c_n, cmd_next_history_eol); Set(Key_up, cmd_prev_history_eol); Set(Key_c_p, cmd_prev_history_eol); le_modes[LE_MODE_VI_INSERT].keymap = t; le_modes[LE_MODE_VI_COMMAND].default_command = cmd_alert; t = trie_create(); Set(Key_escape, cmd_noop); Set(L"1", cmd_digit_argument); Set(L"2", cmd_digit_argument); Set(L"3", cmd_digit_argument); Set(L"4", cmd_digit_argument); Set(L"5", cmd_digit_argument); Set(L"6", cmd_digit_argument); Set(L"7", cmd_digit_argument); Set(L"8", cmd_digit_argument); Set(L"9", cmd_digit_argument); Set(L"0", cmd_bol_or_digit); Set(Key_c_j, cmd_accept_line); Set(Key_c_m, cmd_accept_line); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_eof, cmd_eof_if_empty); Set(Key_c_d, cmd_eof_if_empty); Set(L"#", cmd_accept_with_hash); Set(L"i", cmd_setmode_viinsert); Set(Key_insert, cmd_setmode_viinsert); Set(Key_c_l, cmd_redraw_all); Set(L"l", cmd_forward_char); Set(L" ", cmd_forward_char); Set(Key_right, cmd_forward_char); Set(L"h", cmd_backward_char); Set(Key_left, cmd_backward_char); Set(Key_backspace, cmd_backward_char); Set(Key_erase, cmd_backward_char); Set(Key_c_h, cmd_backward_char); Set(L"W", cmd_forward_bigword); Set(L"E", cmd_end_of_bigword); Set(L"B", cmd_backward_bigword); Set(L"w", cmd_forward_viword); Set(L"e", cmd_end_of_viword); Set(L"b", cmd_backward_viword); Set(Key_home, cmd_beginning_of_line); Set(L"$", cmd_end_of_line); Set(Key_end, cmd_end_of_line); Set(L"^", cmd_first_nonblank); Set(L"f", cmd_find_char); Set(L"F", cmd_find_char_rev); Set(L"t", cmd_till_char); Set(L"T", cmd_till_char_rev); Set(L";", cmd_refind_char); Set(L",", cmd_refind_char_rev); Set(L"x", cmd_kill_char); Set(Key_delete, cmd_kill_char); Set(L"X", cmd_backward_kill_char); Set(L"P", cmd_put_before); Set(L"p", cmd_put); Set(L"u", cmd_undo); Set(L"U", cmd_undo_all); Set(Key_c_r, cmd_cancel_undo); Set(L".", cmd_redo); Set(L"|", cmd_go_to_column); Set(L"r", cmd_vi_replace_char); Set(L"I", cmd_vi_insert_beginning); Set(L"a", cmd_vi_append); Set(L"A", cmd_vi_append_to_eol); Set(L"R", cmd_vi_replace); Set(L"~", cmd_vi_switch_case_char); Set(L"y", cmd_vi_yank); Set(L"Y", cmd_vi_yank_to_eol); Set(L"d", cmd_vi_delete); Set(L"D", cmd_vi_delete_to_eol); Set(L"c", cmd_vi_change); Set(L"C", cmd_vi_change_to_eol); Set(L"S", cmd_vi_change_line); Set(L"s", cmd_vi_substitute); Set(L"_", cmd_vi_append_last_bigword); Set(L"@", cmd_vi_exec_alias); Set(L"v", cmd_vi_edit_and_accept); Set(L"=", cmd_vi_complete_list); Set(L"*", cmd_vi_complete_all); Set(Key_backslash, cmd_vi_complete_max); Set(L"?", cmd_vi_search_forward); Set(L"/", cmd_vi_search_backward); Set(L"n", cmd_search_again); Set(L"N", cmd_search_again_rev); Set(L"G", cmd_oldest_history_bol); Set(L"g", cmd_return_history_bol); Set(L"j", cmd_next_history_bol); Set(L"+", cmd_next_history_bol); Set(Key_down, cmd_next_history_bol); Set(Key_c_n, cmd_next_history_bol); Set(L"k", cmd_prev_history_bol); Set(L"-", cmd_prev_history_bol); Set(Key_up, cmd_prev_history_bol); Set(Key_c_p, cmd_prev_history_bol); le_modes[LE_MODE_VI_COMMAND].keymap = t; le_modes[LE_MODE_VI_SEARCH].default_command = cmd_srch_self_insert; t = trie_create(); Set(Key_c_v, cmd_expect_verbatim); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_c_l, cmd_redraw_all); Set(Key_backslash, cmd_srch_self_insert); Set(Key_backspace, cmd_srch_backward_delete_char); Set(Key_erase, cmd_srch_backward_delete_char); Set(Key_c_h, cmd_srch_backward_delete_char); Set(Key_kill, cmd_srch_backward_delete_line); Set(Key_c_u, cmd_srch_backward_delete_line); Set(Key_c_j, cmd_srch_accept_search); Set(Key_c_m, cmd_srch_accept_search); Set(Key_escape, cmd_srch_abort_search); le_modes[LE_MODE_VI_SEARCH].keymap = t; le_modes[LE_MODE_EMACS].default_command = cmd_self_insert; t = trie_create(); Set(Key_backslash, cmd_self_insert); Set(Key_escape Key_c_i, cmd_insert_tab); Set(Key_c_q, cmd_expect_verbatim); Set(Key_c_v, cmd_expect_verbatim); Set(Key_escape "0", cmd_digit_argument); Set(Key_escape "1", cmd_digit_argument); Set(Key_escape "2", cmd_digit_argument); Set(Key_escape "3", cmd_digit_argument); Set(Key_escape "4", cmd_digit_argument); Set(Key_escape "5", cmd_digit_argument); Set(Key_escape "6", cmd_digit_argument); Set(Key_escape "7", cmd_digit_argument); Set(Key_escape "8", cmd_digit_argument); Set(Key_escape "9", cmd_digit_argument); Set(Key_escape "-", cmd_digit_argument); Set(Key_c_j, cmd_accept_line); Set(Key_c_m, cmd_accept_line); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_eof, cmd_eof_or_delete); Set(Key_c_d, cmd_eof_or_delete); Set(Key_escape "#", cmd_accept_with_hash); Set(Key_c_l, cmd_redraw_all); Set(Key_right, cmd_forward_char); Set(Key_c_f, cmd_forward_char); Set(Key_left, cmd_backward_char); Set(Key_c_b, cmd_backward_char); Set(Key_escape "f", cmd_forward_emacsword); Set(Key_escape "F", cmd_forward_emacsword); Set(Key_escape "b", cmd_backward_emacsword); Set(Key_escape "B", cmd_backward_emacsword); Set(Key_home, cmd_beginning_of_line); Set(Key_c_a, cmd_beginning_of_line); Set(Key_end, cmd_end_of_line); Set(Key_c_e, cmd_end_of_line); Set(Key_c_rb, cmd_find_char); Set(Key_escape Key_c_rb, cmd_find_char_rev); Set(Key_delete, cmd_delete_char); Set(Key_backspace, cmd_backward_delete_char); Set(Key_erase, cmd_backward_delete_char); Set(Key_c_h, cmd_backward_delete_char); Set(Key_escape "d", cmd_kill_emacsword); Set(Key_escape "D", cmd_kill_emacsword); Set(Key_escape Key_backspace, cmd_backward_kill_emacsword); Set(Key_escape Key_erase, cmd_backward_kill_emacsword); Set(Key_escape Key_c_h, cmd_backward_kill_emacsword); Set(Key_c_k, cmd_forward_kill_line); Set(Key_kill, cmd_backward_kill_line); Set(Key_c_u, cmd_backward_kill_line); Set(Key_c_x Key_backspace, cmd_backward_kill_line); Set(Key_c_x Key_erase, cmd_backward_kill_line); Set(Key_c_y, cmd_put_left); Set(Key_escape "y", cmd_put_pop); Set(Key_escape "Y", cmd_put_pop); Set(Key_c_w, cmd_backward_kill_bigword); Set(Key_c_ul, cmd_undo); Set(Key_c_x Key_c_u, cmd_undo); Set(Key_c_x Key_kill, cmd_undo); Set(Key_escape Key_c_r, cmd_undo_all); Set(Key_escape "r", cmd_undo_all); Set(Key_escape "R", cmd_undo_all); Set(Key_tab, cmd_complete_next_candidate); Set(Key_btab, cmd_complete_prev_candidate); Set(Key_escape "=", cmd_complete_list); Set(Key_escape "?", cmd_complete_list); Set(Key_escape "*", cmd_complete_all); Set(Key_c_t, cmd_emacs_transpose_chars); Set(Key_escape "t", cmd_emacs_transpose_words); Set(Key_escape "T", cmd_emacs_transpose_words); Set(Key_escape "l", cmd_emacs_downcase_word); Set(Key_escape "L", cmd_emacs_downcase_word); Set(Key_escape "u", cmd_emacs_upcase_word); Set(Key_escape "U", cmd_emacs_upcase_word); Set(Key_escape "c", cmd_emacs_capitalize_word); Set(Key_escape "C", cmd_emacs_capitalize_word); Set(Key_escape Key_backslash, cmd_emacs_delete_horizontal_space); Set(Key_escape " ", cmd_emacs_just_one_space); Set(Key_c_s, cmd_emacs_search_forward); Set(Key_c_r, cmd_emacs_search_backward); Set(Key_escape "<", cmd_oldest_history_eol); Set(Key_escape ">", cmd_return_history_eol); Set(Key_down, cmd_next_history_eol); Set(Key_c_n, cmd_next_history_eol); Set(Key_up, cmd_prev_history_eol); Set(Key_c_p, cmd_prev_history_eol); le_modes[LE_MODE_EMACS].keymap = t; le_modes[LE_MODE_EMACS_SEARCH].default_command = cmd_srch_self_insert; t = trie_create(); Set(Key_c_v, cmd_expect_verbatim); Set(Key_c_m, cmd_accept_line); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_c_l, cmd_redraw_all); Set(Key_backslash, cmd_srch_self_insert); Set(Key_backspace, cmd_srch_backward_delete_char); Set(Key_erase, cmd_srch_backward_delete_char); Set(Key_c_h, cmd_srch_backward_delete_char); Set(Key_kill, cmd_srch_backward_delete_line); Set(Key_c_u, cmd_srch_backward_delete_line); Set(Key_c_s, cmd_srch_continue_forward); Set(Key_c_r, cmd_srch_continue_backward); Set(Key_c_j, cmd_srch_accept_search); Set(Key_escape, cmd_srch_accept_search); Set(Key_c_g, cmd_srch_abort_search); le_modes[LE_MODE_EMACS_SEARCH].keymap = t; le_modes[LE_MODE_CHAR_EXPECT].default_command = cmd_expect_char; t = trie_create(); Set(Key_c_v, cmd_expect_verbatim); Set(Key_interrupt, cmd_abort_line); Set(Key_c_c, cmd_abort_line); Set(Key_backslash, cmd_expect_char); Set(Key_escape, cmd_abort_expect_char); le_modes[LE_MODE_CHAR_EXPECT].keymap = t; #undef Set } /* Sets the editing mode to the one specified by `id'. */ void le_set_mode(le_mode_id_T id) { assert(id < LE_MODE_N); le_current_mode = le_id_to_mode(id); } /* Generates completion candidates for editing command names matching the * pattern. */ /* The prototype of this function is declared in "complete.h". */ void generate_bindkey_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_BINDKEY)) return; le_compdebug("adding lineedit command name candidates"); if (!le_compile_cpatterns(compopt)) return; for (size_t i = 0; i < sizeof commands / sizeof *commands; i++) if (le_match_comppatterns(compopt, commands[i].name)) le_new_candidate(CT_BINDKEY, malloc_mbstowcs(commands[i].name), NULL, compopt); } /********** Built-in **********/ static int print_all_commands(void); static int set_key_binding( le_mode_id_T mode, const wchar_t *keyseq, const wchar_t *commandname) __attribute__((nonnull)); static le_command_func_T *get_command_from_name(const char *name) __attribute__((nonnull,pure)); static int command_name_compare(const void *p1, const void *p2) __attribute__((nonnull,pure)); static int print_binding(le_mode_id_T mode, const wchar_t *keyseq) __attribute__((nonnull)); static int print_binding_main( void *mode, const wchar_t *keyseq, le_command_func_T *cmd) __attribute__((nonnull)); static const char *get_command_name(le_command_func_T *command) __attribute__((nonnull,const)); /* Options for the "bindkey" built-in. */ const struct xgetopt_T bindkey_options[] = { { L'v', L"vi-insert", OPTARG_NONE, false, NULL, }, { L'a', L"vi-command", OPTARG_NONE, false, NULL, }, { L'e', L"emacs", OPTARG_NONE, false, NULL, }, { L'l', L"list", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "bindkey" built-in, which accepts the following options: * -v: select the "vi-insert" mode * -a: select the "vi-command" mode * -e: select the "emacs" mode * -l: list names of available commands */ int bindkey_builtin(int argc, void **argv) { bool list = false; le_mode_id_T mode = LE_MODE_N; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, bindkey_options, 0)) != NULL) { switch (opt->shortopt) { case L'a': mode = LE_MODE_VI_COMMAND; break; case L'e': mode = LE_MODE_EMACS; break; case L'v': mode = LE_MODE_VI_INSERT; break; case L'l': list = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } le_keymap_init(); if (list) { if (!validate_operand_count(argc - xoptind, 0, 0)) return Exit_ERROR; if (mode != LE_MODE_N) { xerror(0, Ngt("option combination is invalid")); return Exit_ERROR; } return print_all_commands(); } if (mode == LE_MODE_N) { xerror(0, Ngt("no option is specified")); return Exit_ERROR; } switch (argc - xoptind) { case 0:; /* print all key bindings */ le_mode_T *m = le_id_to_mode(mode); return trie_foreachw(m->keymap, print_binding_main, m); case 1: return print_binding(mode, ARGV(xoptind)); case 2: return set_key_binding(mode, ARGV(xoptind), ARGV(xoptind + 1)); default: return too_many_operands_error(2); } } /* Prints all available commands to the standard output. */ int print_all_commands(void) { for (size_t i = 0; i < sizeof commands / sizeof *commands; i++) if (!xprintf("%s\n", commands[i].name)) return Exit_FAILURE; return Exit_SUCCESS; } /* Binds the specified key sequence to the specified command. * If `commandname' is L"-", the binding is removed. * If the specified command is not found, it is an error. */ int set_key_binding( le_mode_id_T mode, const wchar_t *keyseq, const wchar_t *commandname) { if (keyseq[0] == L'\0') { xerror(0, Ngt("cannot bind an empty key sequence")); return Exit_FAILURE; } if (wcscmp(commandname, L"-") == 0) { /* delete key binding */ register trie_T *t = le_modes[mode].keymap; t = trie_removew(t, keyseq); le_modes[mode].keymap = t; } else { /* set key binding */ char *mbsname = malloc_wcstombs(commandname); if (mbsname == NULL) { xerror(EILSEQ, Ngt("unexpected error")); return Exit_FAILURE; } le_command_func_T *cmd = get_command_from_name(mbsname); free(mbsname); if (cmd) { register trie_T *t = le_modes[mode].keymap; t = trie_setw(t, keyseq, (trievalue_T) { .cmdfunc = cmd }); le_modes[mode].keymap = t; } else { xerror(0, Ngt("no such editing command `%ls'"), commandname); return Exit_FAILURE; } } return Exit_SUCCESS; } /* Returns the command function of the specified name. * Returns a null pointer if no such command is found. */ le_command_func_T *get_command_from_name(const char *name) { struct command_name_pair *cnp = bsearch(name, commands, sizeof commands / sizeof *commands, sizeof *commands, command_name_compare); return (cnp != NULL) ? cnp->command : 0; } int command_name_compare(const void *p1, const void *p2) { return strcmp((const char *) p1, ((const struct command_name_pair *) p2)->name); } /* Prints the binding for the given key sequence. */ int print_binding(le_mode_id_T mode, const wchar_t *keyseq) { trieget_T tg = trie_getw(le_modes[mode].keymap, keyseq); if ((tg.type & TG_EXACTMATCH) && tg.matchlength == wcslen(keyseq)) { return print_binding_main( le_id_to_mode(mode), keyseq, tg.value.cmdfunc); } else { xerror(0, Ngt("key sequence `%ls' is not bound"), keyseq); return Exit_FAILURE; } } /* Prints a command to restore the specified key binding. * `mode' must be a pointer to one of the modes in `le_modes'. */ int print_binding_main( void *mode, const wchar_t *keyseq, le_command_func_T *cmd) { const char *format; char modechar; wchar_t *keyseqquote; const char *commandname; switch (le_mode_to_id(mode)) { case LE_MODE_VI_INSERT: modechar = 'v'; break; case LE_MODE_VI_COMMAND: modechar = 'a'; break; case LE_MODE_VI_SEARCH: modechar = 'V'; break; case LE_MODE_EMACS: modechar = 'e'; break; case LE_MODE_EMACS_SEARCH: modechar = 'E'; break; case LE_MODE_CHAR_EXPECT: modechar = 'c'; break; default: assert(false); } if (keyseq[0] == L'-') format = "bindkey -%c -- %ls %s\n"; else format = "bindkey -%c %ls %s\n"; keyseqquote = quote_as_word(keyseq); commandname = get_command_name(cmd); xprintf(format, modechar, keyseqquote, commandname); free(keyseqquote); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Returns the name of the specified command. */ const char *get_command_name(le_command_func_T *command) { for (size_t i = 0; i < sizeof commands / sizeof *commands; i++) if (commands[i].command == command) return commands[i].name; return NULL; } #if YASH_ENABLE_HELP const char bindkey_help[] = Ngt( "set or print key bindings for line-editing" ); const char bindkey_syntax[] = Ngt( "\tbindkey -aev [key_sequence [command]]\n" "\tbindkey -l\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/keymap.h000066400000000000000000000043101354143602500156510ustar00rootroot00000000000000/* Yash: yet another shell */ /* keymap.h: mappings from keys to functions */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_KEYMAP_H #define YASH_KEYMAP_H #include #include "key.h" #include "../xgetopt.h" /* This structure describes an editing mode. */ typedef struct le_mode_T { le_command_func_T *default_command; struct trienode_T /* trie_T */ *keymap; } le_mode_T; /* mode indices */ typedef enum le_mode_id_T { LE_MODE_VI_INSERT, LE_MODE_VI_COMMAND, LE_MODE_VI_SEARCH, LE_MODE_EMACS, LE_MODE_EMACS_SEARCH, LE_MODE_CHAR_EXPECT, LE_MODE_N, // number of modes } le_mode_id_T; extern le_mode_T le_modes[LE_MODE_N]; extern le_mode_T *le_current_mode; static inline le_mode_id_T le_mode_to_id(le_mode_T *mode) __attribute__((nonnull,const)); static inline le_mode_T *le_id_to_mode(le_mode_id_T modeid) __attribute__((const)); #define LE_CURRENT_MODE (le_mode_to_id(le_current_mode)) extern void le_keymap_init(void); extern void le_set_mode(le_mode_id_T id); extern int bindkey_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char bindkey_help[], bindkey_syntax[]; #endif extern const struct xgetopt_T bindkey_options[]; /* Returns the mode ID of the specified mode. */ le_mode_id_T le_mode_to_id(le_mode_T *mode) { return (le_mode_id_T) (mode - le_modes); } /* Returns a pointer to the mode specified by the specified ID. */ le_mode_T *le_id_to_mode(le_mode_id_T modeid) { return &le_modes[modeid]; } #endif /* YASH_KEYMAP_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/lineedit.c000066400000000000000000000276331354143602500161700ustar00rootroot00000000000000/* Yash: yet another shell */ /* lineedit.c: command line editing */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "lineedit.h" #include #include #include #include #include #include #include #include #include "../option.h" #include "../sig.h" #include "../strbuf.h" #include "../util.h" #include "../variable.h" #include "display.h" #include "editing.h" #include "key.h" #include "keymap.h" #include "terminfo.h" #include "trie.h" static void reader_init(bool trap); static void reader_finalize(void); static void read_next(void); static int get_read_timeout(void) __attribute__((pure)); static char pop_prebuffer(void); static inline bool has_meta_bit(char c) __attribute__((pure)); static inline trieget_T make_trieget(const wchar_t *keyseq) __attribute__((nonnull,const)); static void append_to_second_buffer(wchar_t wc); /* The state of line-editing. */ enum le_state_T le_state; /* The state of editing. */ enum le_editstate_T le_editstate; /* Do line-editing using the specified prompts. * The prompts may contain backslash escapes specified in "input.c". * If `trap' is true, traps are handled while waiting for input. * The result is returned as a newly malloced wide string, including the * trailing newline. It is assigned to `*resultp' iff the return value is * INPUT_OK. Returns INPUT_ERROR iff failed to set up the terminal. */ inputresult_T le_readline( struct promptset_T prompt, bool trap, wchar_t **resultp) { assert(is_interactive_now); assert(le_state == LE_STATE_INACTIVE); if (!isatty(STDIN_FILENO) || !isatty(STDERR_FILENO) || !le_setupterm(true) || !le_set_terminal()) return INPUT_ERROR; le_state = LE_STATE_ACTIVE; le_keymap_init(); le_editing_init(); le_display_init(prompt); reader_init(trap); le_editstate = LE_EDITSTATE_EDITING; do read_next(); while (le_editstate == LE_EDITSTATE_EDITING); wchar_t *resultline; reader_finalize(); le_display_finalize(); resultline = le_editing_finalize(); le_restore_terminal(); le_state = LE_STATE_INACTIVE; switch (le_editstate) { case LE_EDITSTATE_DONE: *resultp = resultline; return INPUT_OK; case LE_EDITSTATE_ERROR: free(resultline); return INPUT_EOF; case LE_EDITSTATE_INTERRUPTED: free(resultline); return INPUT_INTERRUPTED; case LE_EDITSTATE_EDITING: assert(false); } assert(false); } /* Clears the edit line and restores the terminal state. * Does nothing if line-editing is not active. */ void le_suspend_readline(void) { if (le_state == LE_STATE_ACTIVE) { le_state = LE_STATE_SUSPENDED; le_display_clear(false); le_restore_terminal(); } } /* Resumes line-editing suspended by `le_suspend_readline'. * Does nothing if the line-editing is not suspended. */ void le_resume_readline(void) { if (le_state == LE_STATE_SUSPENDED) { le_state = LE_STATE_ACTIVE; le_setupterm(true); le_set_terminal(); le_display_update(true); le_display_flush(); } } /* Re-retrieves the terminfo and reprints everything. */ /* When the display size is changed, this function is called. */ void le_display_size_changed(void) { if (le_state == LE_STATE_ACTIVE) { le_display_clear(false); le_setupterm(true); le_display_update(true); le_display_flush(); } } /********** Input Reading **********/ /* True if traps should be handled while reading. */ static bool reader_trap; /* Temporary buffer that contains bytes that are treated as input. */ static xstrbuf_T reader_prebuffer; /* Temporary buffer that contains input bytes. */ static xstrbuf_T reader_first_buffer; /* Conversion state used in reading input. */ static mbstate_t reader_state; /* Temporary buffer that contains input converted into wide characters. */ static xwcsbuf_T reader_second_buffer; /* If true, next input will be inserted directly to the main buffer. */ bool le_next_verbatim; /* Initializes the state of the reader. */ void reader_init(bool trap) { reader_trap = trap; sb_init(&reader_first_buffer); memset(&reader_state, 0, sizeof reader_state); wb_init(&reader_second_buffer); le_next_verbatim = false; } /* Frees memory used by the reader. */ void reader_finalize(void) { sb_destroy(&reader_first_buffer); wb_destroy(&reader_second_buffer); } /* Reads the next byte from the standard input and take all the corresponding * actions. * May return without doing anything if a signal was caught, if the shell was * interrupted, etc. * The caller must check `le_state' after this function returned. This function * should be called repeatedly while `le_editstate' is LE_STATE_EDITING. */ void read_next(void) { static bool incomplete_wchar = false; static bool keycode_ambiguous = false; assert(le_editstate == LE_EDITSTATE_EDITING); bool timeout = false; char c = pop_prebuffer(); if (c != '\0') goto direct_first_buffer; le_display_update(true); le_display_flush(); /* wait for and read the next byte */ switch (wait_for_input(STDIN_FILENO, reader_trap, keycode_ambiguous ? get_read_timeout() : -1)) { case W_READY: switch (read(STDIN_FILENO, &c, 1)) { case 0: incomplete_wchar = keycode_ambiguous = false; le_editstate = LE_EDITSTATE_ERROR; return; case 1: break; case -1: switch (errno) { case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif case EINTR: return; default: xerror(errno, Ngt("cannot read input")); incomplete_wchar = keycode_ambiguous = false; le_editstate = LE_EDITSTATE_ERROR; return; } default: assert(false); } if (has_meta_bit(c)) { sb_ccat(&reader_first_buffer, ESCAPE_CHAR); sb_ccat(&reader_first_buffer, c & ~META_BIT); } else { direct_first_buffer: sb_ccat(&reader_first_buffer, c); } break; case W_TIMED_OUT: timeout = true; break; case W_INTERRUPTED: le_editstate = LE_EDITSTATE_INTERRUPTED; return; case W_ERROR: le_editstate = LE_EDITSTATE_ERROR; return; } if (incomplete_wchar || le_next_verbatim) goto process_wide; /* process the content in the first buffer */ keycode_ambiguous = false; while (reader_first_buffer.length > 0) { /* check if `reader_first_buffer' is a special sequence */ int firstchar = (unsigned char) reader_first_buffer.contents[0]; trieget_T tg; if (firstchar == le_interrupt_char) tg = make_trieget(Key_interrupt); else if (firstchar == le_eof_char) tg = make_trieget(Key_eof); else if (firstchar == le_kill_char) tg = make_trieget(Key_kill); else if (firstchar == le_erase_char) tg = make_trieget(Key_erase); else tg = trie_get(le_keycodes, reader_first_buffer.contents, reader_first_buffer.length); switch (tg.type) { case TG_NOMATCH: goto process_wide; case TG_AMBIGUOUS: if (timeout) { case TG_EXACTMATCH: sb_remove(&reader_first_buffer, 0, tg.matchlength); wb_cat(&reader_second_buffer, tg.value.keyseq); continue; } else { keycode_ambiguous = true; } /* falls thru! */ case TG_PREFIXMATCH: goto process_keymap; } } /* convert bytes in the first buffer into wide characters and append to the * second buffer */ process_wide: while (reader_first_buffer.length > 0) { wchar_t wc; size_t n = mbrtowc(&wc, reader_first_buffer.contents, reader_first_buffer.length, &reader_state); incomplete_wchar = false; switch (n) { case 0: // read null character sb_clear(&reader_first_buffer); append_to_second_buffer(L'\0'); break; case (size_t) -1: // conversion error lebuf_print_alert(true); memset(&reader_state, 0, sizeof reader_state); sb_clear(&reader_first_buffer); le_next_verbatim = false; goto process_keymap; case (size_t) -2: // more bytes needed incomplete_wchar = true; sb_clear(&reader_first_buffer); goto process_keymap; default: sb_remove(&reader_first_buffer, 0, n); append_to_second_buffer(wc); break; } } /* process key mapping for wide characters in the second buffer */ process_keymap: while (reader_second_buffer.length > 0) { register wchar_t c; trieget_T tg = trie_getw( le_current_mode->keymap, reader_second_buffer.contents); switch (tg.type) { case TG_NOMATCH: assert(reader_second_buffer.length > 0); if (reader_second_buffer.length > 1) c = L'\0'; else c = reader_second_buffer.contents[0]; le_invoke_command(le_current_mode->default_command, c); wb_clear(&reader_second_buffer); break; case TG_EXACTMATCH: assert(tg.matchlength > 0); c = reader_second_buffer.contents[tg.matchlength - 1]; le_invoke_command(tg.value.cmdfunc, c); wb_remove(&reader_second_buffer, 0, tg.matchlength); break; case TG_PREFIXMATCH: case TG_AMBIGUOUS: return; /* For an unmatched control sequence, `cmd_self_insert' should not * receive any part of the sequence as argument (because the sequence * should not be inserted in the main buffer), so the input is passed * to the default command iff the input is a usual character. * For a matched control sequence, the last character of the sequence * is passed to the command so that commands like `cmd_digit_argument' * work. */ } if (le_editstate != LE_EDITSTATE_EDITING) break; } } /* Returns a timeout value to be passed to the `wait_for_input' function. * The value is taken from the $YASH_LE_TIMEOUT variable. */ int get_read_timeout(void) { #ifndef LE_TIMEOUT_DEFAULT #define LE_TIMEOUT_DEFAULT 100 #endif const wchar_t *v = getvar(L VAR_YASH_LE_TIMEOUT); if (v != NULL) { int i; if (xwcstoi(v, 0, &i)) return i; } return LE_TIMEOUT_DEFAULT; } /* Appends `s' to the prebuffer. String `s' is freed in this function. */ void le_append_to_prebuffer(char *s) { if (reader_prebuffer.contents == NULL) sb_initwith(&reader_prebuffer, s); else sb_catfree(&reader_prebuffer, s); } /* Removes and returns the next character in the prebuffer if available; * otherwise, returns '\0'. */ char pop_prebuffer(void) { if (reader_prebuffer.contents != NULL) { char result = reader_prebuffer.contents[0]; sb_remove(&reader_prebuffer, 0, 1); if (reader_prebuffer.length == 0) { sb_destroy(&reader_prebuffer); reader_prebuffer.contents = NULL; } return result; } return '\0'; } /* Tests if the specified character has the meta flag set. */ bool has_meta_bit(char c) { switch (shopt_le_convmeta) { case SHOPT_YES: break; case SHOPT_NO: return false; case SHOPT_AUTO: if (le_meta_bit8) break; else return false; } return (c & META_BIT) != 0; } trieget_T make_trieget(const wchar_t *keyseq) { return (trieget_T) { .type = TG_EXACTMATCH, .matchlength = 1, .value.keyseq = keyseq, }; } /* Appends the specified character to the second buffer. * If `le_next_verbatim' is true, the character is directly processed by the * default command. */ void append_to_second_buffer(wchar_t wc) { if (le_next_verbatim) { le_next_verbatim = false; le_invoke_command(le_current_mode->default_command, wc); } else { switch (wc) { case L'\0': break; case L'\\': wb_cat(&reader_second_buffer, Key_backslash); break; default: wb_wccat(&reader_second_buffer, wc); break; } } } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/lineedit.h000066400000000000000000000036741354143602500161740ustar00rootroot00000000000000/* Yash: yet another shell */ /* lineedit.h: command line editing */ /* (C) 2007-2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_LINEEDIT_H #define YASH_LINEEDIT_H #include #include "../input.h" enum le_state_T { LE_STATE_INACTIVE = 0, LE_STATE_ACTIVE = 1 << 0, LE_STATE_SUSPENDED = 1 << 1, LE_STATE_COMPLETING = 1 << 2, }; #define le_state_is_compdebug \ ((le_state & (LE_STATE_SUSPENDED | LE_STATE_COMPLETING)) \ == (LE_STATE_SUSPENDED | LE_STATE_COMPLETING)) enum le_editstate_T { LE_EDITSTATE_EDITING, // editing is on-going LE_EDITSTATE_DONE, // `le_readline' should return (successful) LE_EDITSTATE_ERROR, // `le_readline' should return (unsuccessful) LE_EDITSTATE_INTERRUPTED, // `le_readline' should return (interrupted) }; extern enum le_state_T le_state; extern enum le_editstate_T le_editstate; extern inputresult_T le_readline( struct promptset_T prompt, _Bool trap, wchar_t **resultp) __attribute__((nonnull,warn_unused_result)); extern void le_suspend_readline(void); extern void le_resume_readline(void); extern void le_display_size_changed(void); extern _Bool le_next_verbatim; extern void le_append_to_prebuffer(char *s) __attribute__((nonnull)); #endif /* YASH_LINEEDIT_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/terminfo.c000066400000000000000000000646541354143602500162220ustar00rootroot00000000000000/* Yash: yet another shell */ /* terminfo.c: interface to terminfo and termios */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "terminfo.h" #include #include #if HAVE_CURSES_H # include #elif HAVE_NCURSES_H # include #elif HAVE_NCURSES_NCURSES_H # include #elif HAVE_NCURSESW_NCURSES_H # include #endif #include #include #include #include #if HAVE_TIOCGWINSZ # include #endif #if HAVE_TERM_H # include #elif HAVE_NCURSES_TERM_H # include #elif HAVE_NCURSESW_TERM_H # include #endif #include #include #include "../option.h" #include "../sig.h" #include "../util.h" #include "../variable.h" #include "display.h" #include "key.h" #include "trie.h" /********** TERMINFO **********/ /* terminfo capabilities */ #define TI_am "am" #define TI_bel "bel" #define TI_blink "blink" #define TI_bold "bold" #define TI_clear "clear" #define TI_colors "colors" #define TI_cols "cols" #define TI_cr "cr" #define TI_cub "cub" #define TI_cub1 "cub1" #define TI_cud "cud" #define TI_cud1 "cud1" #define TI_cuf "cuf" #define TI_cuf1 "cuf1" #define TI_cuu "cuu" #define TI_cuu1 "cuu1" #define TI_dim "dim" #define TI_ed "ed" #define TI_el "el" #define TI_flash "flash" #define TI_invis "invis" #define TI_kBEG "kBEG" #define TI_kCAN "kCAN" #define TI_kCMD "kCMD" #define TI_kCPY "kCPY" #define TI_kCRT "kCRT" #define TI_kDC "kDC" #define TI_kDL "kDL" #define TI_kEND "kEND" #define TI_kEOL "kEOL" #define TI_kEXT "kEXT" #define TI_kFND "kFND" #define TI_kHLP "kHLP" #define TI_kHOM "kHOM" #define TI_kIC "kIC" #define TI_kLFT "kLFT" #define TI_kMOV "kMOV" #define TI_kMSG "kMSG" #define TI_kNXT "kNXT" #define TI_kOPT "kOPT" #define TI_kPRT "kPRT" #define TI_kPRV "kPRV" #define TI_kRDO "kRDO" #define TI_kRES "kRES" #define TI_kRIT "kRIT" #define TI_kRPL "kRPL" #define TI_kSAV "kSAV" #define TI_kSPD "kSPD" #define TI_kUND "kUND" #define TI_ka1 "ka1" #define TI_ka3 "ka3" #define TI_kb2 "kb2" #define TI_kbeg "kbeg" #define TI_kbs "kbs" #define TI_kc1 "kc1" #define TI_kc3 "kc3" #define TI_kcan "kcan" #define TI_kcbt "kcbt" #define TI_kclo "kclo" #define TI_kclr "kclr" #define TI_kcmd "kcmd" #define TI_kcpy "kcpy" #define TI_kcrt "kcrt" #define TI_kctab "kctab" #define TI_kcub1 "kcub1" #define TI_kcud1 "kcud1" #define TI_kcuf1 "kcuf1" #define TI_kcuu1 "kcuu1" #define TI_kdch1 "kdch1" #define TI_kdl1 "kdl1" #define TI_ked "ked" #define TI_kel "kel" #define TI_kend "kend" #define TI_kent "kent" #define TI_kext "kext" #define TI_kf0 "kf0" #define TI_kf1 "kf1" #define TI_kf2 "kf2" #define TI_kf3 "kf3" #define TI_kf4 "kf4" #define TI_kf5 "kf5" #define TI_kf6 "kf6" #define TI_kf7 "kf7" #define TI_kf8 "kf8" #define TI_kf9 "kf9" #define TI_kf10 "kf10" #define TI_kf11 "kf11" #define TI_kf12 "kf12" #define TI_kf13 "kf13" #define TI_kf14 "kf14" #define TI_kf15 "kf15" #define TI_kf16 "kf16" #define TI_kf17 "kf17" #define TI_kf18 "kf18" #define TI_kf19 "kf19" #define TI_kf20 "kf20" #define TI_kf21 "kf21" #define TI_kf22 "kf22" #define TI_kf23 "kf23" #define TI_kf24 "kf24" #define TI_kf25 "kf25" #define TI_kf26 "kf26" #define TI_kf27 "kf27" #define TI_kf28 "kf28" #define TI_kf29 "kf29" #define TI_kf30 "kf30" #define TI_kf31 "kf31" #define TI_kf32 "kf32" #define TI_kf33 "kf33" #define TI_kf34 "kf34" #define TI_kf35 "kf35" #define TI_kf36 "kf36" #define TI_kf37 "kf37" #define TI_kf38 "kf38" #define TI_kf39 "kf39" #define TI_kf40 "kf40" #define TI_kf41 "kf41" #define TI_kf42 "kf42" #define TI_kf43 "kf43" #define TI_kf44 "kf44" #define TI_kf45 "kf45" #define TI_kf46 "kf46" #define TI_kf47 "kf47" #define TI_kf48 "kf48" #define TI_kf49 "kf49" #define TI_kf50 "kf50" #define TI_kf51 "kf51" #define TI_kf52 "kf52" #define TI_kf53 "kf53" #define TI_kf54 "kf54" #define TI_kf55 "kf55" #define TI_kf56 "kf56" #define TI_kf57 "kf57" #define TI_kf58 "kf58" #define TI_kf59 "kf59" #define TI_kf60 "kf60" #define TI_kf61 "kf61" #define TI_kf62 "kf62" #define TI_kf63 "kf63" #define TI_kfnd "kfnd" #define TI_khlp "khlp" #define TI_khome "khome" #define TI_khts "khts" #define TI_kich1 "kich1" #define TI_kil1 "kil1" #define TI_kind "kind" #define TI_kll "kll" #define TI_km "km" #define TI_kmous "kmous" #define TI_kmov "kmov" #define TI_kmrk "kmrk" #define TI_kmsg "kmsg" #define TI_knp "knp" #define TI_knxt "knxt" #define TI_kopn "kopn" #define TI_kopt "kopt" #define TI_kpp "kpp" #define TI_kprt "kprt" #define TI_kprv "kprv" #define TI_krdo "krdo" #define TI_kref "kref" #define TI_kres "kres" #define TI_krfr "krfr" #define TI_kri "kri" #define TI_krmir "krmir" #define TI_krpl "krpl" #define TI_krst "krst" #define TI_ksav "ksav" #define TI_kslt "kslt" #define TI_kspd "kspd" #define TI_ktbc "ktbc" #define TI_kund "kund" #define TI_lines "lines" #define TI_msgr "msgr" #define TI_nel "nel" #define TI_op "op" #define TI_rev "rev" #define TI_rmkx "rmkx" #define TI_setab "setab" #define TI_setaf "setaf" #define TI_setb "setb" #define TI_setf "setf" #define TI_sgr0 "sgr0" #define TI_smkx "smkx" #define TI_smso "smso" #define TI_smul "smul" #define TI_xenl "xenl" #define TI_xmc "xmc" /* This flag is set to true when the terminfo database needs to be refreshed * because the $TERM variable has been changed. */ _Bool le_need_term_update = 1; /* Number of lines, columns and colors available in the current terminal. */ /* Initialized in `le_setupterm'. */ int le_lines, le_columns, le_colors; /* The value of the "xmc" capability. */ int le_ti_xmc; /* Whether the terminal has the "xenl" and "msgr" flags set. */ _Bool le_ti_xenl, le_ti_msgr; /* True if the meta key inputs character whose 8th bit is set. */ /* Used only if the `shopt_le_convmeta' option is "auto". */ _Bool le_meta_bit8; /* Strings sent by terminal when special key is pressed. * The values of entries are `keyseq'. */ trie_T *le_keycodes = NULL; /* True if the terminal is set to the keyboard-transmit mode. */ static _Bool transmit_mode = 0; static inline int is_strcap_valid(const char *s) __attribute__((const)); static void set_up_keycodes(void); static _Bool try_print_cap(const char *capname) __attribute__((nonnull)); static void move_cursor(char *capone, char *capmul, long count, int affcnt) __attribute__((nonnull)); static _Bool move_cursor_1(char *capone, long count) __attribute__((nonnull)); static _Bool move_cursor_mul(char *capmul, long count, int affcnt) __attribute__((nonnull)); static void print_color_code(long color, char *seta, char *set) __attribute__((nonnull)); static void print_smkx(void); static void print_rmkx(void); static int putchar_stderr(int c); /* Checks if the result of `tigetstr' is valid. */ int is_strcap_valid(const char *s) { return s != NULL && s != (const char *) -1; } /* Calls `setupterm' and checks if terminfo data is available. * If `bypass' is true and `le_need_term_update' is false, the terminfo data * are not refreshed and only the terminal size (`le_lines' and `le_columns') * is adjusted. * Returns true iff successful. */ _Bool le_setupterm(_Bool bypass) { static _Bool once = 0; int err; reset_sigwinch(); assert(once || le_need_term_update); #if HAVE_TIOCGWINSZ if (bypass && !le_need_term_update) { struct winsize ws; if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_row > 0 && ws.ws_col > 0) { if (getenv(VAR_LINES) == NULL) le_lines = ws.ws_row; if (getenv(VAR_COLUMNS) == NULL) le_columns = ws.ws_col; return 1; } } #else (void) bypass; #endif /* HAVE_TIOCGWINSZ */ if (once) del_curterm(cur_term); if (setupterm(NULL, STDERR_FILENO, &err) == ERR) return 0; once = 1; if (tigetflag(TI_am) <= 0) return 0; if (!is_strcap_valid(tigetstr(TI_cub1)) && !is_strcap_valid(tigetstr(TI_cub))) return 0; if (!is_strcap_valid(tigetstr(TI_cuf1)) && !is_strcap_valid(tigetstr(TI_cuf))) return 0; if (!is_strcap_valid(tigetstr(TI_cud1)) && !is_strcap_valid(tigetstr(TI_cud))) return 0; if (!is_strcap_valid(tigetstr(TI_cuu1)) && !is_strcap_valid(tigetstr(TI_cuu))) return 0; if (!is_strcap_valid(tigetstr(TI_el))) return 0; le_lines = tigetnum(TI_lines); le_columns = tigetnum(TI_cols); if (le_lines <= 0 || le_columns <= 0) return 0; le_colors = tigetnum(TI_colors); le_ti_xmc = tigetnum(TI_xmc); le_ti_xenl = tigetflag(TI_xenl) > 0; le_ti_msgr = tigetflag(TI_msgr) > 0; le_meta_bit8 = tigetflag(TI_km) > 0; set_up_keycodes(); le_need_term_update = 0; return 1; } /* Initializes `le_keycodes'. */ void set_up_keycodes(void) { trie_destroy(le_keycodes); static const struct charmap { char c; const wchar_t *keyseq; } charmap[] = { { '\01', Key_c_a, }, { '\02', Key_c_b, }, { '\03', Key_c_c, }, { '\04', Key_c_d, }, { '\05', Key_c_e, }, { '\06', Key_c_f, }, { '\07', Key_c_g, }, { '\10', Key_c_h, }, { '\11', Key_c_i, }, { '\12', Key_c_j, }, { '\13', Key_c_k, }, { '\14', Key_c_l, }, { '\15', Key_c_m, }, { '\16', Key_c_n, }, { '\17', Key_c_o, }, { '\20', Key_c_p, }, { '\21', Key_c_q, }, { '\22', Key_c_r, }, { '\23', Key_c_s, }, { '\24', Key_c_t, }, { '\25', Key_c_u, }, { '\26', Key_c_v, }, { '\27', Key_c_w, }, { '\30', Key_c_x, }, { '\31', Key_c_y, }, { '\32', Key_c_z, }, { '\33', Key_c_lb, }, { '\34', Key_c_bs, }, { '\35', Key_c_rb, }, { '\36', Key_c_hat, }, { '\37', Key_c_ul, }, { '\77', Key_c_del, }, }; static const struct keymap { char *capability; const wchar_t *keyseq; } keymap [] = { { TI_ka1, Key_a1, }, { TI_ka3, Key_a3, }, { TI_kb2, Key_b2, }, { TI_kbs, Key_backspace, }, { TI_kbeg, Key_beg, }, { TI_kcbt, Key_btab, }, { TI_kc1, Key_c1, }, { TI_kc3, Key_c3, }, { TI_kcan, Key_cancel, }, { TI_ktbc, Key_catab, }, { TI_kclr, Key_clear, }, { TI_kclo, Key_close, }, { TI_kcmd, Key_command, }, { TI_kcpy, Key_copy, }, { TI_kcrt, Key_create, }, { TI_kctab, Key_ctab, }, { TI_kdch1, Key_delete, }, { TI_kdl1, Key_dl, }, { TI_kcud1, Key_down, }, { TI_krmir, Key_eic, }, { TI_kend, Key_end, }, { TI_kent, Key_enter, }, { TI_kel, Key_eol, }, { TI_ked, Key_eos, }, { TI_kext, Key_exit, }, { TI_kf0, Key_f00, }, { TI_kf1, Key_f01, }, { TI_kf2, Key_f02, }, { TI_kf3, Key_f03, }, { TI_kf4, Key_f04, }, { TI_kf5, Key_f05, }, { TI_kf6, Key_f06, }, { TI_kf7, Key_f07, }, { TI_kf8, Key_f08, }, { TI_kf9, Key_f09, }, { TI_kf10, Key_f10, }, { TI_kf11, Key_f11, }, { TI_kf12, Key_f12, }, { TI_kf13, Key_f13, }, { TI_kf14, Key_f14, }, { TI_kf15, Key_f15, }, { TI_kf16, Key_f16, }, { TI_kf17, Key_f17, }, { TI_kf18, Key_f18, }, { TI_kf19, Key_f19, }, { TI_kf20, Key_f20, }, { TI_kf21, Key_f21, }, { TI_kf22, Key_f22, }, { TI_kf23, Key_f23, }, { TI_kf24, Key_f24, }, { TI_kf25, Key_f25, }, { TI_kf26, Key_f26, }, { TI_kf27, Key_f27, }, { TI_kf28, Key_f28, }, { TI_kf29, Key_f29, }, { TI_kf30, Key_f30, }, { TI_kf31, Key_f31, }, { TI_kf32, Key_f32, }, { TI_kf33, Key_f33, }, { TI_kf34, Key_f34, }, { TI_kf35, Key_f35, }, { TI_kf36, Key_f36, }, { TI_kf37, Key_f37, }, { TI_kf38, Key_f38, }, { TI_kf39, Key_f39, }, { TI_kf40, Key_f40, }, { TI_kf41, Key_f41, }, { TI_kf42, Key_f42, }, { TI_kf43, Key_f43, }, { TI_kf44, Key_f44, }, { TI_kf45, Key_f45, }, { TI_kf46, Key_f46, }, { TI_kf47, Key_f47, }, { TI_kf48, Key_f48, }, { TI_kf49, Key_f49, }, { TI_kf50, Key_f50, }, { TI_kf51, Key_f51, }, { TI_kf52, Key_f52, }, { TI_kf53, Key_f53, }, { TI_kf54, Key_f54, }, { TI_kf55, Key_f55, }, { TI_kf56, Key_f56, }, { TI_kf57, Key_f57, }, { TI_kf58, Key_f58, }, { TI_kf59, Key_f59, }, { TI_kf60, Key_f60, }, { TI_kf61, Key_f61, }, { TI_kf62, Key_f62, }, { TI_kf63, Key_f63, }, { TI_kfnd, Key_find, }, { TI_khlp, Key_help, }, { TI_khome, Key_home, }, { TI_kich1, Key_insert, }, { TI_kil1, Key_il, }, { TI_kcub1, Key_left, }, { TI_kll, Key_ll, }, { TI_kmrk, Key_mark, }, { TI_kmsg, Key_message, }, { TI_kmous, Key_mouse, }, { TI_kmov, Key_move, }, { TI_knxt, Key_next, }, { TI_knp, Key_pagedown, }, { TI_kopn, Key_open, }, { TI_kopt, Key_options, }, { TI_kpp, Key_pageup, }, { TI_kprv, Key_previous, }, { TI_kprt, Key_print, }, { TI_krdo, Key_redo, }, { TI_kref, Key_reference, }, { TI_krfr, Key_refresh, }, { TI_krpl, Key_replace, }, { TI_krst, Key_restart, }, { TI_kres, Key_resume, }, { TI_kcuf1, Key_right, }, { TI_ksav, Key_save, }, { TI_kslt, Key_select, }, { TI_kind, Key_sf, }, { TI_kri, Key_sr, }, { TI_khts, Key_stab, }, { TI_kspd, Key_suspend, }, { TI_kund, Key_undo, }, { TI_kcuu1, Key_up, }, { TI_kBEG, Key_s_beg, }, { TI_kCAN, Key_s_cancel, }, { TI_kCMD, Key_s_command, }, { TI_kCPY, Key_s_copy, }, { TI_kCRT, Key_s_create, }, { TI_kDC, Key_s_delete, }, { TI_kDL, Key_s_dl, }, { TI_kEND, Key_s_end, }, { TI_kEOL, Key_s_eol, }, { TI_kEXT, Key_s_exit, }, { TI_kFND, Key_s_find, }, { TI_kHLP, Key_s_help, }, { TI_kHOM, Key_s_home, }, { TI_kIC, Key_s_insert, }, { TI_kLFT, Key_s_left, }, { TI_kMSG, Key_s_message, }, { TI_kMOV, Key_s_move, }, { TI_kNXT, Key_s_next, }, { TI_kOPT, Key_s_options, }, { TI_kPRV, Key_s_prev, }, { TI_kPRT, Key_s_print, }, { TI_kRDO, Key_s_redo, }, { TI_kRPL, Key_s_replace, }, { TI_kRIT, Key_s_right, }, { TI_kRES, Key_s_resume, }, { TI_kSAV, Key_s_save, }, { TI_kSPD, Key_s_suspend, }, { TI_kUND, Key_s_undo, }, }; trie_T *t = trie_create(); t = trie_set_null(t, (trievalue_T) { .keyseq = Key_c_at }); for (size_t i = 0; i < sizeof charmap / sizeof *charmap; i++) { if (xiscntrl(charmap[i].c)) t = trie_set(t, (char []) { charmap[i].c, '\0', }, (trievalue_T) { .keyseq = charmap[i].keyseq }); } for (size_t i = 0; i < sizeof keymap / sizeof *keymap; i++) { const char *seq = tigetstr(keymap[i].capability); if (is_strcap_valid(seq)) t = trie_set(t, seq, (trievalue_T) { .keyseq = keymap[i].keyseq }); } le_keycodes = t; } /* Tries to print the specified capability string to the print buffer. * Returns 1 iff successful. */ _Bool try_print_cap(const char *capname) { char *v = tigetstr((char *) capname); if (is_strcap_valid(v) && v[0] != '\0') if (tputs(v, 1, lebuf_putchar) != ERR) return 1; return 0; } /* Prints the "cr" code to the print buffer. * (carriage return: move cursor to first char of line) */ void lebuf_print_cr(void) { #if 0 char *v = tigetstr(TI_cr); if (is_strcap_valid(v) && v[0] != '\0') tputs(v, 1, lebuf_putchar); else #endif lebuf_putchar('\r'); lebuf.pos.column = 0; } /* Prints the "nel" code to the print buffer. * (newline: move cursor to first char of next line) */ void lebuf_print_nel(void) { #if 0 char *v = tigetstr(TI_nel); if (is_strcap_valid(v) && v[0] != '\0') tputs(v, 1, lebuf_putchar); else #endif lebuf_putchar('\r'), lebuf_putchar('\n'); lebuf.pos.line++, lebuf.pos.column = 0; } /* Moves the cursor. * `capone' must be one of "cub1", "cuf1", "cud1", "cuu1". * `capmul' must be one of "cub", "cuf", "cud", "cuu". */ void move_cursor(char *capone, char *capmul, long count, int affcnt) { if (count > 0) { if (count == 1) { if (!move_cursor_1(capone, 1)) move_cursor_mul(capmul, 1, affcnt); } else { if (!move_cursor_mul(capmul, count, affcnt)) move_cursor_1(capone, count); } } } _Bool move_cursor_1(char *capone, long count) { char *v = tigetstr(capone); if (is_strcap_valid(v) && v[0] != '\0') { do tputs(v, 1, lebuf_putchar); while (--count > 0); return 1; } else { return 0; } } _Bool move_cursor_mul(char *capmul, long count, int affcnt) { char *v = tigetstr(capmul); if (is_strcap_valid(v)) { v = tparm(v, count, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); if (v != NULL) { tputs(v, affcnt, lebuf_putchar); return 1; } } return 0; } /* Prints the "cub"/"cub1" code to the print buffer. * (move cursor backward by `count' columns) * `count' must be small enough not to go beyond the screen bounds. */ void lebuf_print_cub(long count) { move_cursor(TI_cub1, TI_cub, count, 1); lebuf.pos.column -= count; assert(lebuf.pos.column >= 0); } /* Prints the "cuf"/"cuf1" code to the print buffer. * (move cursor forward by `count' columns) * `count' must be small enough not to go beyond the screen bounds. */ void lebuf_print_cuf(long count) { move_cursor(TI_cuf1, TI_cuf, count, 1); lebuf.pos.column += count; assert(lebuf.pos.column < lebuf.maxcolumn); } /* Prints the "cud"/"cud1" code to the print buffer. * (move cursor down by `count' lines) * `count' must be small enough not to go beyond screen bounds. * The cursor must be on the first column. */ void lebuf_print_cud(long count) { assert(lebuf.pos.column == 0); move_cursor(TI_cud1, TI_cud, count, count + 1); lebuf.pos.line += count; } /* Prints the "cuu"/"cuu1" code to the print buffer. * (move cursor up by `count' lines) * `count' must be small enough not to go beyond screen bounds. * The cursor must be on the first column. */ void lebuf_print_cuu(long count) { assert(lebuf.pos.column == 0); move_cursor(TI_cuu1, TI_cuu, count, count + 1); lebuf.pos.line -= count; } /* Prints the "el" code to the print buffer. (clear to end of line) * Returns true iff successful. */ _Bool lebuf_print_el(void) { return try_print_cap(TI_el); } /* Prints the "ed" code to the print buffer if available. * (clear to end of screen) * Returns true iff successful. */ _Bool lebuf_print_ed(void) { return try_print_cap(TI_ed); } /* Prints the "clear" code if available. (clear whole screen) * Returns true iff successful. */ _Bool lebuf_print_clear(void) { if (try_print_cap(TI_clear)) { lebuf.pos.line = lebuf.pos.column = 0; return 1; } else { return 0; } } /* Prints the "op" code. (set color pairs to default) * Returns true iff successful. */ _Bool lebuf_print_op(void) { return try_print_cap(TI_op); } /* Prints the "setf"/"setaf" code. */ void lebuf_print_setfg(long color) { print_color_code(color, TI_setaf, TI_setf); } /* Prints the "setb"/"setab" code. */ void lebuf_print_setbg(long color) { print_color_code(color, TI_setab, TI_setb); } void print_color_code(long color, char *seta, char *set) { if (le_colors < 16) color &= 0x7L; if (le_colors <= color) return; char *v = tigetstr(seta); if (!is_strcap_valid(v) || v[0] == '\0') { v = tigetstr(set); color = ((color & 0x1L) << 2) | (color & 0x2L) | ((color & 0x4L) >> 2) | (color & ~0x7L); } if (is_strcap_valid(v)) { v = tparm(v, color, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); if (v != NULL) tputs(v, 1, lebuf_putchar); } } /* Prints the "sgr0" code to the print buffer. * (reset the terminal font) * Returns true iff successful. */ _Bool lebuf_print_sgr0(void) { return try_print_cap(TI_sgr0); } /* Prints the "smso" code to the print buffer. (start standout mode) * Returns true iff successful. */ _Bool lebuf_print_smso(void) { if (try_print_cap(TI_smso)) { if (le_ti_xmc > 0) lebuf_update_position(le_ti_xmc); return 1; } else { return 0; } } /* Prints the "smul" code to the print buffer. (start underline mode) * Returns true iff successful. */ _Bool lebuf_print_smul(void) { return try_print_cap(TI_smul); } /* Prints the "rev" code to the print buffer. (start reverse mode) * Returns true iff successful. */ _Bool lebuf_print_rev(void) { return try_print_cap(TI_rev); } /* Prints the "blink" code to the print buffer. (start blink mode) * Returns true iff successful. */ _Bool lebuf_print_blink(void) { return try_print_cap(TI_blink); } /* Prints the "dim" code to the print buffer. (start dim mode) * Returns true iff successful. */ _Bool lebuf_print_dim(void) { return try_print_cap(TI_dim); } /* Prints the "bold" code to the print buffer. (start bold mode) * Returns true iff successful. */ _Bool lebuf_print_bold(void) { return try_print_cap(TI_bold); } /* Prints the "invis" code to the print buffer. (start invisible mode) * Returns true iff successful. */ _Bool lebuf_print_invis(void) { return try_print_cap(TI_invis); } /* Prints the "flash" or "bel" code to alert the user. * If `direct_stderr' is true, the output is sent to the standard error; * otherwise, to the print buffer. */ void lebuf_print_alert(_Bool direct_stderr) { int (*outfunc)(int) = direct_stderr ? putchar_stderr : lebuf_putchar; char *v = NULL; if (shopt_le_visiblebell) v = tigetstr(TI_flash); if (!is_strcap_valid(v) || v[0] == '\0') v = tigetstr(TI_bel); if (is_strcap_valid(v) && v[0] == '\0') tputs(v, 1, outfunc); else outfunc('\a'); } /* Prints the "smkx" code to the standard error and sets the `transmit_mode' * flag. */ void print_smkx(void) { char *v = tigetstr(TI_smkx); if (is_strcap_valid(v)) { tputs(v, 1, putchar_stderr); transmit_mode = 1; } } /* Prints the "rmkx" code to the standard error if the `transmit_mode' flag is * set. The flag is cleared in this function. */ void print_rmkx(void) { if (transmit_mode) { char *v = tigetstr(TI_rmkx); if (is_strcap_valid(v)) tputs(v, 1, putchar_stderr); transmit_mode = 0; } } /* Like `putchar', but prints to `stderr'. */ int putchar_stderr(int c) { return fputc(c, stderr); } /********** TERMIOS **********/ /* Special characters. */ int le_eof_char, le_kill_char, le_interrupt_char, le_erase_char; static struct termios original_terminal_state; static inline int normchar(cc_t c) __attribute__((const)); static void to_raw_mode(struct termios *term, _Bool isig) __attribute__((nonnull)); static inline int xtcgetattr(int fd, struct termios *term) __attribute__((nonnull)); static inline int xtcsetattr(int fd, int opt, const struct termios *term) __attribute__((nonnull)); /* Sets the terminal to the "raw" mode. * The current state is saved as `original_terminal_state'. * Returns true iff the terminal (the standard input) has been successfully * prepared. */ _Bool le_set_terminal(void) { struct termios term; /* First we call `tcdrain' to get a clean state and to ensure the shell is * in the foreground. */ tcdrain(STDIN_FILENO); /* get the original state */ if (!le_save_terminal()) return 0; term = original_terminal_state; le_eof_char = normchar(term.c_cc[VEOF]); le_kill_char = normchar(term.c_cc[VKILL]); le_interrupt_char = normchar(term.c_cc[VINTR]); le_erase_char = normchar(term.c_cc[VERASE]); /* set attributes */ to_raw_mode(&term, 0); if (xtcsetattr(STDIN_FILENO, TCSADRAIN, &term) != 0) goto fail; /* check if the attributes are properly set */ if (xtcgetattr(STDIN_FILENO, &term) != 0) goto fail; if ((term.c_iflag & (INLCR | ICRNL)) || (term.c_lflag & (ECHO | ICANON)) || (term.c_cc[VTIME] != 0) || (term.c_cc[VMIN] != 0)) goto fail; // XXX it should be configurable whether we print smkx or not. print_smkx(); return 1; fail: xtcsetattr(STDIN_FILENO, TCSADRAIN, &original_terminal_state); return 0; } int normchar(cc_t c) { if (c == _POSIX_VDISABLE) return -1; else return (unsigned char) c; } /* Saves the current terminal state in `original_terminal_state'. * This function flushes the standard error before saving the state. * Returns true iff the standard input is a terminal and the terminal state was * successfully saved. */ _Bool le_save_terminal(void) { fflush(stderr); return xtcgetattr(STDIN_FILENO, &original_terminal_state) >= 0; } /* Restores the terminal to the original state saved in * `original_terminal_state'. * This function flushes the standard error before restoring the state. * Returns true iff the standard input is a terminal and the terminal state was * successfully restored. */ _Bool le_restore_terminal(void) { print_rmkx(); fflush(stderr); return xtcsetattr(STDIN_FILENO, TCSADRAIN, &original_terminal_state) >= 0; } /* Sets the ISIG flag of the terminal, which specifies whether, when the user * enters the INTR/QUIT/SUSP character, a corresponding signal is sent to the * foreground process(es). * If `allow' is true, a signal is sent. If `allow' is false, a signal is not * sent. * This function can be used only when the terminal is set to the "raw" mode * using the `le_set_terminal' function. */ _Bool le_allow_terminal_signal(_Bool allow) { struct termios term = original_terminal_state; to_raw_mode(&term, allow); return xtcsetattr(STDIN_FILENO, TCSADRAIN, &term) == 0; } /* Modifies the specified termios structure into the "raw" mode values. * If `isig' is true, the ISIG flag is not cleared from `term->c_lflag'. */ void to_raw_mode(struct termios *term, _Bool isig) { term->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | INLCR | IGNCR | ICRNL | IXON); term->c_iflag |= IGNPAR; term->c_lflag &= ~(ECHO | ICANON | IEXTEN) & (isig ? ~0 : ~ISIG); term->c_cc[VTIME] = 0; term->c_cc[VMIN] = 0; } /* Calls `tcgetattr'. * If it returns EINTR, re-calls it. */ int xtcgetattr(int fd, struct termios *term) { int result; do result = tcgetattr(fd, term); while (result != 0 && errno == EINTR); return result; } /* Calls `tcsetattr'. * If it returns EINTR, re-calls it. */ int xtcsetattr(int fd, int opt, const struct termios *term) { int result; do result = tcsetattr(fd, opt, term); while (result != 0 && errno == EINTR); return result; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/terminfo.h000066400000000000000000000046101354143602500162110ustar00rootroot00000000000000/* Yash: yet another shell */ /* terminfo.h: interface to terminfo and termios */ /* (C) 2007-2010 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_TERMINFO_H #define YASH_TERMINFO_H extern _Bool le_need_term_update; extern int le_lines, le_columns, le_colors; extern int le_ti_xmc; extern _Bool le_ti_am, le_ti_xenl, le_ti_msgr; extern _Bool le_meta_bit8; extern struct trienode_T /* trie_T */ *le_keycodes; extern _Bool le_setupterm(_Bool bypass); enum le_color { LE_COLOR_BLACK = 0, LE_COLOR_RED = 1, LE_COLOR_GREEN = 2, LE_COLOR_YELLOW = 3, LE_COLOR_BLUE = 4, LE_COLOR_MAGENTA = 5, LE_COLOR_CYAN = 6, LE_COLOR_WHITE = 7, }; extern void lebuf_print_cr(void); extern void lebuf_print_nel(void); extern void lebuf_print_cub(long count); extern void lebuf_print_cuf(long count); extern void lebuf_print_cud(long count); extern void lebuf_print_cuu(long count); extern _Bool lebuf_print_el(void); extern _Bool lebuf_print_ed(void); extern _Bool lebuf_print_clear(void); extern _Bool lebuf_print_op(void); extern void lebuf_print_setfg(long color); extern void lebuf_print_setbg(long color); extern _Bool lebuf_print_sgr0(void); extern _Bool lebuf_print_smso(void); extern _Bool lebuf_print_smul(void); extern _Bool lebuf_print_rev(void); extern _Bool lebuf_print_blink(void); extern _Bool lebuf_print_dim(void); extern _Bool lebuf_print_bold(void); extern _Bool lebuf_print_invis(void); extern void lebuf_print_alert(_Bool direct_stderr); extern int le_eof_char, le_kill_char, le_interrupt_char, le_erase_char; extern _Bool le_set_terminal(void); extern _Bool le_save_terminal(void); extern _Bool le_restore_terminal(void); extern _Bool le_allow_terminal_signal(_Bool allow); #endif /* YASH_TERMINFO_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/trie.c000066400000000000000000000333631354143602500153330ustar00rootroot00000000000000/* Yash: yet another shell */ /* trie.c: trie library for lineedit */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../common.h" #include "trie.h" #include #include #include #include #include #include #include #include "../strbuf.h" #include "../util.h" typedef union triekey_T { char as_char; wchar_t as_wchar; } triekey_T; typedef struct trieentry_T { triekey_T key; struct trienode_T *child; } trieentry_T; typedef struct trienode_T { bool valuevalid; trievalue_T value; size_t count; struct trieentry_T entries[]; } trienode_T; #define RAISE_COUNT(count) ((count) | 3) static inline bool isempty(const trienode_T *node) __attribute__((nonnull,pure)); static trienode_T *ensure_size(trienode_T *node, size_t count) __attribute__((nonnull,malloc,warn_unused_result)); static inline ssize_t search(const trienode_T *node, char c) __attribute__((nonnull,pure)); static ssize_t binarysearch(const trieentry_T *e, size_t count, char key) __attribute__((nonnull,pure)); static inline ssize_t searchw(const trienode_T *node, wchar_t c) __attribute__((nonnull,pure)); static ssize_t binarysearchw(const trieentry_T *e, size_t count, wchar_t key) __attribute__((nonnull,pure)); static trienode_T *insert_entry(trienode_T *node, size_t index, triekey_T key) __attribute__((nonnull,malloc,warn_unused_result)); static trienode_T *shrink(trienode_T *node) __attribute__((nonnull,malloc,warn_unused_result)); static int foreachw(const trienode_T *t, int (*func)(void *v, const wchar_t *key, le_command_func_T *cmd), void *v, xwcsbuf_T *buf) __attribute__((nonnull(1,2,4))); static const trieentry_T *most_probable_child(const trienode_T *node) __attribute__((nonnull,pure)); /* Creates a new empty trie. */ trienode_T *trie_create(void) { trienode_T *result = xmallocs( sizeof *result, RAISE_COUNT(0), sizeof *result->entries); result->valuevalid = false; result->count = 0; return result; } /* Checks if the node is empty. */ /* An empty node can be freed. */ bool isempty(const trienode_T *node) { return node->count == 0 && !node->valuevalid; } /* Reallocates the node to ensure the node size enough for the specified entry * count. */ trienode_T *ensure_size(trienode_T *node, size_t count) { count = RAISE_COUNT(count); if (RAISE_COUNT(node->count) >= count) return node; else return xreallocs(node, sizeof *node, count, sizeof *node->entries); } /* Search the node for the entry of the specified byte character key. * If the entry is found, the index to it is returned. * If not found, `-index-1' is returned where `index' is the index that would be * returned if the entry was in the trie. */ ssize_t search(const trienode_T *node, char key) { return binarysearch(node->entries, node->count, key); } ssize_t binarysearch(const trieentry_T *e, size_t count, char key) { ssize_t offset = 0; while (count > 0) { size_t i = count / 2; char ekey = e[offset + i].key.as_char; if (key == ekey) return offset + i; else if (key < ekey) count = i; else offset += i + 1, count -= i + 1; } return -offset - 1; } /* Search the node for the entry of the specified wide character key. * If the entry is found, the index to it is returned. * If not found, `-index-1' is returned where `index' is the index that would be * returned if the entry was in the trie. */ ssize_t searchw(const trienode_T *node, wchar_t key) { return binarysearchw(node->entries, node->count, key); } ssize_t binarysearchw(const trieentry_T *e, size_t count, wchar_t key) { ssize_t offset = 0; while (count > 0) { size_t i = count / 2; wchar_t ekey = e[offset + i].key.as_wchar; if (key == ekey) return offset + i; else if (key < ekey) count = i; else offset += i + 1, count -= i + 1; } return -offset - 1; } /* Creates a new child at `index'. */ trienode_T *insert_entry(trienode_T *node, size_t index, triekey_T key) { assert(index <= node->count); node = ensure_size(node, node->count + 1); if (index < node->count) memmove(&node->entries[index + 1], &node->entries[index], sizeof *node->entries * (node->count - index)); node->entries[index].key = key; node->entries[index].child = trie_create(); node->count++; return node; } /* Adds a mapping from `keystr' to `v'. * The existing mapping for `keystr' is lost if any. */ trienode_T *trie_set(trienode_T *node, const char *keystr, trievalue_T v) { if (keystr[0] == '\0') { node->valuevalid = true; node->value = v; } else { ssize_t index = search(node, keystr[0]); if (index < 0) { index = -(index + 1); node = insert_entry(node, (size_t) index, (triekey_T) { .as_char = keystr[0] }); } node->entries[index].child = trie_set(node->entries[index].child, &keystr[1], v); } return node; } /* Adds a mapping from "\0" to `v'. * The existing mapping for `keystr' is lost if any. */ trienode_T *trie_set_null(trienode_T *node, trievalue_T v) { ssize_t index = search(node, '\0'); if (index < 0) { index = -(index + 1); node = insert_entry(node, (size_t) index, (triekey_T) { .as_char = '\0' }); } node->entries[index].child = trie_set(node->entries[index].child, "", v); return node; } /* Adds a mapping from `keywcs' to `v'. * The existing mapping for `keywcs' is lost if any. */ trienode_T *trie_setw(trienode_T *node, const wchar_t *keywcs, trievalue_T v) { if (keywcs[0] == L'\0') { node->valuevalid = true; node->value = v; } else { ssize_t index = searchw(node, keywcs[0]); if (index < 0) { index = -(index + 1); node = insert_entry(node, (size_t) index, (triekey_T) { .as_wchar = keywcs[0] }); } node->entries[index].child = trie_setw(node->entries[index].child, &keywcs[1], v); } return node; } /* Decreases the child count. The node may be reallocated in this function. */ trienode_T *shrink(trienode_T *node) { size_t oldcount, newcount; oldcount = RAISE_COUNT(node->count); node->count--; newcount = RAISE_COUNT(node->count); if (oldcount != newcount) node = xreallocs(node, sizeof *node, newcount, sizeof *node->entries); return node; } /* Removes the mapping for the specified byte string key. */ trienode_T *trie_remove(trienode_T *node, const char *keystr) { if (keystr[0] == '\0') { node->valuevalid = false; } else { ssize_t index = search(node, keystr[0]); if (index >= 0) { node->entries[index].child = trie_remove(node->entries[index].child, &keystr[1]); if (isempty(node->entries[index].child)) { free(node->entries[index].child); memmove(&node->entries[index], &node->entries[index + 1], sizeof *node->entries * (node->count - index - 1)); node = shrink(node); } } } return node; } /* Removes the mapping for the specified wide string key. */ trienode_T *trie_removew(trienode_T *node, const wchar_t *keywcs) { if (keywcs[0] == L'\0') { node->valuevalid = false; } else { ssize_t index = searchw(node, keywcs[0]); if (index >= 0) { node->entries[index].child = trie_removew(node->entries[index].child, &keywcs[1]); if (isempty(node->entries[index].child)) { free(node->entries[index].child); memmove(&node->entries[index], &node->entries[index + 1], sizeof *node->entries * (node->count - index - 1)); node = shrink(node); } } } return node; } /* Matches `keystr' with the entries of the trie. * This function does not treat '\0' as the end of a string. The length of * `keystr' is given by `keylen'. * `value' of the return value is valid only if the TG_EXACTMATCH flag is in * `type'. */ trieget_T trie_get(const trienode_T *t, const char *keystr, size_t keylen) { trieget_T result = { .type = TG_NOMATCH, .matchlength = 0, }; if (keylen == 0) { if (t->valuevalid) result.type |= TG_EXACTMATCH, result.value = t->value; if (t->count > 0) result.type |= TG_PREFIXMATCH; return result; } ssize_t index = search(t, keystr[0]); if (index < 0) { if (t->valuevalid) result.type |= TG_EXACTMATCH, result.value = t->value; return result; } result = trie_get(t->entries[index].child, &keystr[1], keylen - 1); if (result.type & TG_EXACTMATCH) { result.matchlength++; } else if (t->valuevalid) { result.type |= TG_EXACTMATCH; result.matchlength = 0; result.value = t->value; } return result; } /* Matches `keywcs' with the entries of the trie. * `value' of the return value is valid only if the TG_EXACTMATCH flag is in * `type'. */ trieget_T trie_getw(const trienode_T *t, const wchar_t *keywcs) { trieget_T result = { .type = TG_NOMATCH, .matchlength = 0, }; if (keywcs[0] == L'\0') { if (t->valuevalid) result.type |= TG_EXACTMATCH, result.value = t->value; if (t->count > 0) result.type |= TG_PREFIXMATCH; return result; } ssize_t index = searchw(t, keywcs[0]); if (index < 0) { if (t->valuevalid) result.type |= TG_EXACTMATCH, result.value = t->value; return result; } result = trie_getw(t->entries[index].child, &keywcs[1]); if (result.type & TG_EXACTMATCH) { result.matchlength++; } else if (t->valuevalid) { result.type |= TG_EXACTMATCH; result.matchlength = 0; result.value = t->value; } return result; } /* Calls function `func' for each entry in trie `t'. * Pointer `v' is passed to the function as the first argument. * Returns zero if `func' was called for all the entries. * When `func' returns non-zero, the iteration is aborted and `trie_foreachw' * returns the value returned by `func'. */ int trie_foreachw(const trienode_T *t, int (*func)(void *v, const wchar_t *key, le_command_func_T *cmd), void *v) { xwcsbuf_T buf; int result; wb_init(&buf); result = foreachw(t, func, v, &buf); wb_destroy(&buf); return result; } int foreachw(const trienode_T *t, int func(void *v, const wchar_t *key, le_command_func_T cmd), void *v, xwcsbuf_T *buf) { int result; if (t->valuevalid) { result = func(v, buf->contents, t->value.cmdfunc); if (result != 0) return result; } for (size_t i = 0; i < t->count; i++) { wb_wccat(buf, t->entries[i].key.as_wchar); result = foreachw(t->entries[i].child, func, v, buf); if (result != 0) return result; assert(buf->length > 0); wb_truncate(buf, buf->length - 1); } return 0; } /* Destroys the whole tree. * All the stored data are lost. */ void trie_destroy(trienode_T *node) { if (node != NULL) { for (size_t i = 0; i < node->count; i++) trie_destroy(node->entries[i].child); free(node); } } /********** Functions for prediction **********/ /* Adds the given probability value `p' to each node on the given key string * `keywcs'. */ trienode_T *trie_add_probability( trienode_T *node, const wchar_t *keywcs, double p) { if (node->valuevalid) { node->value.probability += p; } else { node->valuevalid = true; node->value.probability = p; } if (keywcs[0] == L'\0') return node; ssize_t index = searchw(node, keywcs[0]); if (index < 0) { index = -(index + 1); node = insert_entry(node, (size_t) index, (triekey_T) { .as_wchar = keywcs[0] }); } node->entries[index].child = trie_add_probability( node->entries[index].child, &keywcs[1], p); return node; } /* Returns the most probable key as a newly-malloced wide string. * Only keys that start with `skipkey' are considered. The `skipkey' prefix is * not included in the result. * Returns an empty string if no keys start with `skipkey'. */ wchar_t *trie_probable_key(const trienode_T *node, const wchar_t *skipkey) { // Skip the prefix to ignore. while (*skipkey != L'\0') { ssize_t index = searchw(node, *skipkey); if (index < 0) return xwcsdup(L""); node = node->entries[index].child; skipkey++; } // Find the most probable initial. const trieentry_T *entry = most_probable_child(node); if (entry == NULL) return xwcsdup(L""); double threshold = entry->child->value.probability / 2; // Traverse nodes that have the most probability to construct the final // result. Stop traversal when the probability falls below the threshold. // As an exception, traverse blanks regardless of probability so that the // user doesn't have to type a space before continuing typing a word that // follows. xwcsbuf_T key; wb_init(&key); while (entry != NULL && (entry->child->value.probability >= threshold || iswblank(entry->key.as_wchar))) { wb_wccat(&key, entry->key.as_wchar); entry = most_probable_child(entry->child); } return wb_towcs(&key); } /* Find the given node's child that have the most probability value. * Returns NULL iff the node has no children. */ const trieentry_T *most_probable_child(const trienode_T *node) { double max_probability = -HUGE_VAL; const trieentry_T *entry = NULL; for (size_t i = 0; i < node->count; i++) { assert(node->entries[i].child->valuevalid); double probability = node->entries[i].child->value.probability; if (probability > max_probability) { max_probability = probability; entry = &node->entries[i]; } } return entry; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/trie.h000066400000000000000000000051541354143602500153350ustar00rootroot00000000000000/* Yash: yet another shell */ /* trie.h: trie library for lineedit */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_TRIE_H #define YASH_TRIE_H #include #include "key.h" typedef union trievalue_T { const wchar_t *keyseq; le_command_func_T *cmdfunc; double probability; } trievalue_T; typedef struct trienode_T trie_T; typedef struct trieget_T { enum { TG_NOMATCH = 0, TG_EXACTMATCH = 1 << 0, TG_PREFIXMATCH = 1 << 1, TG_AMBIGUOUS = TG_EXACTMATCH | TG_PREFIXMATCH, } type; size_t matchlength; trievalue_T value; } trieget_T; extern trie_T *trie_create(void) __attribute__((malloc,warn_unused_result)); extern trie_T *trie_set(trie_T *t, const char *keystr, trievalue_T v) __attribute__((nonnull(1,2),malloc,warn_unused_result)); extern trie_T *trie_set_null(trie_T *t, trievalue_T v) __attribute__((nonnull(1),malloc,warn_unused_result)); extern trie_T *trie_setw(trie_T *t, const wchar_t *keywcs, trievalue_T v) __attribute__((nonnull(1,2),malloc,warn_unused_result)); extern trie_T *trie_remove(trie_T *t, const char *keystr) __attribute__((nonnull(1,2),malloc,warn_unused_result)); extern trie_T *trie_removew(trie_T *t, const wchar_t *keywcs) __attribute__((nonnull(1,2),malloc,warn_unused_result)); extern trieget_T trie_get(const trie_T *t, const char *keystr, size_t keylen) __attribute__((nonnull)); extern trieget_T trie_getw(const trie_T *t, const wchar_t *keywcs) __attribute__((nonnull)); extern int trie_foreachw(const trie_T *t, int (*func)(void *v, const wchar_t *key, le_command_func_T *cmd), void *v) __attribute__((nonnull(1,2))); extern void trie_destroy(trie_T *t); extern trie_T *trie_add_probability(trie_T *t, const wchar_t *keywcs, double p) __attribute__((nonnull,malloc,warn_unused_result)); extern wchar_t *trie_probable_key(const trie_T *t, const wchar_t *skipkey) __attribute__((nonnull,malloc,warn_unused_result)); #endif /* YASH_TRIE_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/trietest.c000066400000000000000000000056361354143602500162350ustar00rootroot00000000000000// This is a test tool, not part of yash // make trie.o // (cd .. && make strbuf.o) // c99 -o trietest trietest.c trie.o ../strbuf.o ../util.o #include #include #include #include #include #include #include "trie.h" void print(const trie_T *t, const char *key) { trieget_T tg = trie_get(t, key, strlen(key)); printf("%-10s: ", key); switch (tg.type) { case TG_NOMATCH: printf("nomatch %zu\n", tg.matchlength); break; case TG_EXACTMATCH: printf("exact %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; case TG_PREFIXMATCH: printf("prefix %zu\n", tg.matchlength); break; case TG_AMBIGUOUS: printf("ambig %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; default: printf("ERROR: type=%d\n", (int) tg.type); assert(false); } } void print_null(const trie_T *t) { trieget_T tg = trie_get(t, "", 1); printf(" : "); switch (tg.type) { case TG_NOMATCH: printf("nomatch %zu\n", tg.matchlength); break; case TG_EXACTMATCH: printf("exact %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; case TG_PREFIXMATCH: printf("prefix %zu\n", tg.matchlength); break; case TG_AMBIGUOUS: printf("ambig %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; default: printf("ERROR: type=%d\n", (int) tg.type); assert(false); } } int main(int argc, char **argv) { (void) argc, (void) argv; trie_T *t = trie_create(); print_null(t); print(t, ""); #define make_trievalue(s) ((trievalue_T) { .keyseq = (s) }) t = trie_set_null(t, make_trievalue(L"Null")); t = trie_set(t, "abc", make_trievalue(L"ABC")); t = trie_set(t, "abca", make_trievalue(L"ABCA")); t = trie_set(t, "abcb", make_trievalue(L"ABCB")); t = trie_set(t, "abcc", make_trievalue(L"ABCC")); t = trie_set(t, "abcd", make_trievalue(L"")); t = trie_set(t, "abcd", make_trievalue(L"ABCD")); t = trie_set(t, "abcde", make_trievalue(L"ABCDE")); t = trie_set(t, "abce", make_trievalue(L"ABCE")); t = trie_set(t, "abcf", make_trievalue(L"ABCF")); t = trie_set(t, "abcg", make_trievalue(L"ABCG")); t = trie_set(t, "abch", make_trievalue(L"ABCH")); t = trie_set(t, "b", make_trievalue(L"B")); print_null(t); print(t, ""); print(t, "ab"); print(t, "abc"); print(t, "abcd"); print(t, "abcde"); print(t, "abcdex"); print(t, "abcdx"); print(t, "abcx"); print(t, "abx"); print(t, "ax"); print(t, "b"); print(t, "x"); t = trie_remove(t, "b"); t = trie_remove(t, "c"); t = trie_set(t, "a", make_trievalue(L"A")); print(t, "a"); print(t, "ax"); print(t, "ab"); print(t, "abx"); print(t, "abc"); print(t, "abcx"); print(t, "b"); print(t, "x"); trie_destroy(t); exit(EXIT_SUCCESS); } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/lineedit/trietestw.c000066400000000000000000000044431354143602500164170ustar00rootroot00000000000000// This is a test tool, not part of yash // make trie.o // (cd .. && make strbuf.o) // c99 -o trietestw trietestw.c trie.o ../strbuf.o ../util.o #include #include #include #include #include #include "trie.h" void print(const trie_T *t, const wchar_t *key) { trieget_T tg = trie_getw(t, key); printf("%-10ls: ", key); switch (tg.type) { case TG_NOMATCH: printf("nomatch %zu\n", tg.matchlength); break; case TG_EXACTMATCH: printf("exact %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; case TG_PREFIXMATCH: printf("prefix %zu\n", tg.matchlength); break; case TG_AMBIGUOUS: printf("ambig %zu (%ls)\n", tg.matchlength, tg.value.keyseq); break; default: printf("ERROR: type=%d\n", (int) tg.type); assert(false); } } int main(int argc, char **argv) { (void) argc, (void) argv; trie_T *t = trie_create(); print(t, L""); #define make_trievalue(s) ((trievalue_T) { .keyseq = (s) }) t = trie_setw(t, L"abc", make_trievalue(L"ABC")); t = trie_setw(t, L"abca", make_trievalue(L"ABCA")); t = trie_setw(t, L"abcb", make_trievalue(L"ABCB")); t = trie_setw(t, L"abcc", make_trievalue(L"ABCC")); t = trie_setw(t, L"abcd", make_trievalue(L"")); t = trie_setw(t, L"abcd", make_trievalue(L"ABCD")); t = trie_setw(t, L"abcde", make_trievalue(L"ABCDE")); t = trie_setw(t, L"abce", make_trievalue(L"ABCE")); t = trie_setw(t, L"abcf", make_trievalue(L"ABCF")); t = trie_setw(t, L"abcg", make_trievalue(L"ABCG")); t = trie_setw(t, L"abch", make_trievalue(L"ABCH")); t = trie_setw(t, L"b", make_trievalue(L"B")); print(t, L""); print(t, L"ab"); print(t, L"abc"); print(t, L"abcd"); print(t, L"abcde"); print(t, L"abcdex"); print(t, L"abcdx"); print(t, L"abcx"); print(t, L"abx"); print(t, L"ax"); print(t, L"b"); print(t, L"x"); t = trie_removew(t, L"b"); t = trie_removew(t, L"c"); t = trie_setw(t, L"a", make_trievalue(L"A")); print(t, L"a"); print(t, L"ax"); print(t, L"ab"); print(t, L"abx"); print(t, L"abc"); print(t, L"abcx"); print(t, L"b"); print(t, L"x"); trie_destroy(t); exit(EXIT_SUCCESS); } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/mail.c000066400000000000000000000170351354143602500135130ustar00rootroot00000000000000/* Yash: yet another shell */ /* mail.c: mail checking */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "mail.h" #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "expand.h" #include "hashtable.h" #include "option.h" #include "parser.h" #include "plist.h" #include "strbuf.h" #include "util.h" #include "variable.h" /* The type of objects used to remember the status of files for mail checking.*/ typedef struct mailfile_T { #if HAVE_ST_MTIM || HAVE_ST_MTIMESPEC struct timespec mf_mtim; # define mf_mtime mf_mtim.tv_sec #else time_t mf_mtime; # if HAVE_ST_MTIMENSEC || HAVE___ST_MTIMENSEC unsigned long mf_mtimensec; # endif #endif char mf_filename[]; } mailfile_T; static void activate(void); static void inactivate(void); static bool is_time_to_check_mail(void); static void check_mail_and_print_message(void); static void handle_mailpath(wchar_t *paths) __attribute__((nonnull)); static void handle_mailpath_element(const wchar_t *s) __attribute__((nonnull)); static bool is_update(const char *path) __attribute__((nonnull)); static void print_message(const wchar_t *message) __attribute__((nonnull)); /* A hashtable that contains `mailfile_T' objects. * The keys are pointers to the `mf_filename' member of `mailfile_T' objects, * and the values are pointers to the `mailfile_T' objects. * When mail checking is not activated, the capacity of the hashtable is set to * zero. */ static hashtable_T mailfiles; /* The time of last mail check. */ static time_t lastchecktime = 0; /* If it is time to check mail, checks if the mail file is updated, and if so * prints a message. The parsing state must be saved with `save_parse_state' * before calling this function. */ void check_mail(void) { if (is_time_to_check_mail()) check_mail_and_print_message(); } /* Activates `mailfiles'. */ void activate(void) { if (mailfiles.capacity == 0) ht_init(&mailfiles, hashstr, htstrcmp); } /* Inactivates `mailfiles'. */ void inactivate(void) { if (mailfiles.capacity > 0) { ht_destroy(ht_clear(&mailfiles, vfree)); mailfiles.capacity = 0; lastchecktime = 0; } } /* Decides if it is time to check mail now. * Inactivates mail checking and returns false if the $MAILCHECK variable is not * a valid integer. * Sets `lastchecktime' to now if the return value is true. */ bool is_time_to_check_mail(void) { const wchar_t *mailcheck = getvar(L VAR_MAILCHECK); if (mailcheck == NULL || mailcheck[0] == L'\0') { inactivate(); return false; } long interval; if (!xwcstol(mailcheck, 10, &interval) || interval < 0) { inactivate(); return false; } time_t now = time(NULL); if (now == -1 || now - lastchecktime < interval) return false; lastchecktime = now; return true; } /* Checks if the mail file is updated and prints a message if so. */ void check_mail_and_print_message(void) { /* Firstly, check the $MAILPATH variable */ struct get_variable_T mailpath = get_variable(L VAR_MAILPATH); switch (mailpath.type) { case GV_NOTFOUND: break; case GV_SCALAR: activate(); handle_mailpath(mailpath.values[0]); goto mailpath_handled; case GV_ARRAY: case GV_ARRAY_CONCAT: activate(); for (size_t i = 0; i < mailpath.count; i++) handle_mailpath_element(mailpath.values[i]); mailpath_handled: if (mailpath.freevalues) plfree(mailpath.values, free); return; } /* Next, check the $MAIL variable */ const wchar_t *mail = getvar(L VAR_MAIL); if (mail != NULL) { activate(); char *path = malloc_wcstombs(mail); if (path != NULL) { if (is_update(path)) fprintf(stderr, "%s\n", gt("You have new mail.")); free(path); } return; } /* disable mail check since the variables are not set */ inactivate(); } /* Splits the specified string at colons and calls `handle_mailpath_element' for * each component. * This function directly modifies the string. */ void handle_mailpath(wchar_t *paths) { const wchar_t *start; next: start = paths; for (;;) { switch (*paths) { case L'\0': handle_mailpath_element(start); return; case L':': *paths = L'\0'; handle_mailpath_element(start); paths++; goto next; case L'\\': paths++; if (*paths == L'\0') { handle_mailpath_element(start); return; } /* falls thru! */ default: paths++; continue; } } } /* Parses the specified $MAILPATH component and checks for update. */ void handle_mailpath_element(const wchar_t *s) { xstrbuf_T path; mbstate_t state; sb_init(&path); memset(&state, 0, sizeof state); for (;;) { switch (*s) { case L'\0': goto check; case L'%': s++; goto check; case L'\\': s++; if (*s == L'\0') goto check; /* falls thru! */ default: sb_wccat(&path, *s, &state); s++; continue; } } check: sb_wccat(&path, L'\0', &state); if (is_update(path.contents)) { if (*s == L'\0') fprintf(stderr, "%s\n", gt("You have new mail.")); else print_message(s); } sb_destroy(&path); } /* Checks if the specified file is updated. */ bool is_update(const char *path) { struct stat st; if (stat(path, &st) < 0) { st.st_size = 0; st.st_mtime = 0; #if HAVE_ST_MTIM st.st_mtim.tv_nsec = 0; #elif HAVE_ST_MTIMESPEC st.st_mtimespec.tv_nsec = 0; #elif HAVE_ST_MTIMENSEC st.st_mtimensec = 0; #elif HAVE___ST_MTIMENSEC st.__st_mtimensec = 0; #endif } bool result; mailfile_T *mf = ht_get(&mailfiles, path).value; if (mf != NULL) { result = (st.st_size > 0 || posixly_correct) && (st.st_mtime != 0) && (st.st_mtime != mf->mf_mtime #if HAVE_ST_MTIM || st.st_mtim.tv_nsec != mf->mf_mtim.tv_nsec #elif HAVE_ST_MTIMESPEC || st.st_mtimespec.tv_nsec != mf->mf_mtim.tv_nsec #elif HAVE_ST_MTIMENSEC || (unsigned long) st.st_mtimensec != mf->mf_mtimensec #elif HAVE___ST_MTIMENSEC || (unsigned long) st.__st_mtimensec != mf->mf_mtimensec #endif ); } else { mf = xmallocs(sizeof *mf, add(strlen(path), 1), sizeof *mf->mf_filename); strcpy(mf->mf_filename, path); ht_set(&mailfiles, mf->mf_filename, mf); result = false; } #if HAVE_ST_MTIM mf->mf_mtim = st.st_mtim; #elif HAVE_ST_MTIMESPEC mf->mf_mtim = st.st_mtimespec; #else mf->mf_mtime = st.st_mtime; # if HAVE_ST_MTIMENSEC mf->mf_mtimensec = (unsigned long) st.st_mtimensec; # elif HAVE___ST_MTIMENSEC mf->mf_mtimensec = (unsigned long) st.__st_mtimensec; # endif #endif return result; } /* Prints the specified `message' after performing parameter expansion on it. */ void print_message(const wchar_t *message) { /* assuming the parse state is saved */ wchar_t *msg = parse_and_expand_string(message, NULL, true); if (msg != NULL) { fprintf(stderr, "%ls\n", msg); free(msg); } } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/mail.h000066400000000000000000000015571354143602500135220ustar00rootroot00000000000000/* Yash: yet another shell */ /* mail.h: mail checking */ /* (C) 2007-2009 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_MAIL_H #define YASH_MAIL_H void check_mail(void); #endif /* YASH_MAIL_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/makedeps.yash000066400000000000000000000033431354143602500151010ustar00rootroot00000000000000# This script updates *.d files. # (C) 2007-2010 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . if [ -z "${YASH_VERSION-}" ] then printf '%s: This script must be executed by yash.\n' "$0" >&2 exit 126 fi check () { if ! [ -f "$1.d" ] || ! [ -s "$1.d" ] || [ "$1.c" -nt "$1.d" ] then return 1 fi for dep in $(sed -e '1s/^.*://' -e 's/\\$//' "$1.d") do if ! [ -f "$dep" ] then printf '%s: "%s" depends on non-existent file "%s".\n' \ "$0" "$1.d" "$dep" >&2 exit 1 fi if [ "$dep" -nt "$1.d" ] then return 1 fi done return 0 } for file do case $file in (*.c) if ! [ -f "$file" ] then printf '%s: "%s" is not a regular file.\n' "$0" "$file" >&2 exit 1 fi file=${file%.c} if ! check "$file" then printf 'gcc -std=c99 -MM "%s.c" >|"%s.d"\n' "$file" "$file" if ! gcc -std=c99 -MM "${file}.c" >|"${file}.d" then exitstatus=$? printf '%s: cannot update "%s".\n' "$0" "${file}.d" >&2 exit "$exitstatus" fi fi ;; (*) printf '%s: "%s" is not a C source file.\n' "$0" "$file" >&2 exit 1 ;; esac done # vim: set ft=sh ts=8 sts=4 sw=4 noet tw=80: yash-2.49/makesignum.c000066400000000000000000000054301354143602500147250ustar00rootroot00000000000000/* Yash: yet another shell */ /* makesignum.c: outputs string for 'signum.h' contents */ /* (C) 2007-2009 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include #include #include #include #include "siglist.h" int main(void) { setlocale(LC_ALL, ""); int min = 0; wprintf(L"/* signum.h: generated by makesignum */\n\n"); wprintf(L"#ifndef SIGNUM_H\n#define SIGNUM_H\n\n"); wprintf(L"#include \n\n"); for (const signal_T *s = signals; s->no; s++) if (min < s->no) min = s->no; if (min < 100) { wprintf(L"/* an injective function that returns an array index\n"); wprintf(L" * corresponding to the given signal number,\n"); wprintf(L" * which must be a valid non-realtime signal number\n"); wprintf(L" * or zero. */\n"); wprintf(L"__attribute__((const))\n"); wprintf(L"static inline size_t sigindex(int signum) {\n"); wprintf(L" return (size_t) signum;\n"); wprintf(L"}\n\n"); wprintf(L"/* max index returned by sigindex + 1 */\n"); wprintf(L"#define MAXSIGIDX %d\n\n", min + 1); } else { sigset_t ss; size_t v; wprintf(L"/* an injective function that returns an array index\n"); wprintf(L" * corresponding to the given signal number,\n"); wprintf(L" * which must be a valid non-realtime signal number\n"); wprintf(L" * or zero. */\n"); wprintf(L"__attribute__((const))\n"); wprintf(L"static inline size_t sigindex(int signum) {\n"); wprintf(L" switch (signum) {\n"); wprintf(L" case 0 : return 0;\n"); sigemptyset(&ss); v = 1; for (const signal_T *s = signals; s->no; s++) { if (!sigismember(&ss, s->no)) { sigaddset(&ss, s->no); wprintf(L" case SIG%-7s: return %zu;\n", s->name, v++); } } wprintf(L" }\n"); wprintf(L"}\n\n"); wprintf(L"/* max index returned by sigindex + 1 */\n"); wprintf(L"#define MAXSIGIDX %zu\n\n", v); } wprintf(L"/* number of realtime signals that can be handled by yash */\n"); wprintf(L"#define RTSIZE %d\n\n", #if defined SIGRTMIN && defined SIGRTMAX SIGRTMAX - SIGRTMIN + 10 #else 0 #endif ); wprintf(L"#endif\n"); return 0; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/option.c000066400000000000000000000765261354143602500141130ustar00rootroot00000000000000/* Yash: yet another shell */ /* option.c: option settings */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "option.h" #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include "builtin.h" #include "exec.h" #include "job.h" #include "plist.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" /********** Option Definitions **********/ /* The value of special parameter $0. */ const wchar_t *command_name; /* If set, the shell behaves strictly as defined in POSIX. * Corresponds to the --posixly-correct option. */ bool posixly_correct = false; /* If set, this shell is a login shell. * Corresponds to the --login option. */ bool is_login_shell = false; /* If set, this shell is interactive. * In subshells, `is_interactive_now' is set to false while `is_interactive' * remains unchanged. * Correspond to the -i/--interactive option. */ bool is_interactive = false, is_interactive_now = false; /* `shopt_cmdline' is set if the shell is executing a command in the command- * line argument to the shell. `shopt_stdin' is set if the shell is reading * commands to execute from the standard input. * Correspond to the -c and -s options respectively. */ bool shopt_cmdline = false, shopt_stdin = false; /* If set, the shell performs job control. * Corresponds to the -m/--monitor option. */ bool do_job_control = false; /* If set, the shell immediately notifies on change of job status. * Corresponds to the -b/--notify option. */ bool shopt_notify = false; #if YASH_ENABLE_LINEEDIT /* If set, the shell immediately notifies on change of job status during line- * editing. Ignored if `shopt_notify' is set. Corresponds to the --notifyle * option. */ bool shopt_notifyle = false; #endif /* If set, a background job is set to the current job when: * - invoked as an asynchronous command * - resumed in the background by the "bg" command * - stopped * respectively. * Correspond to the --curasync, --curbg, --curstop options. */ bool shopt_curasync = true, shopt_curbg = true, shopt_curstop = true; /* If set, any variable is exported when assigned. * Corresponds to the -a/--allexport option. */ bool shopt_allexport = false; /* If set, when a function is defined, all the commands in the function * are hashed. Corresponds to the -h/--hashondef option. */ bool shopt_hashondef = false; /* If set, the 'for' loop iteration variable will be made local. */ bool shopt_forlocal = true; /* If set, when a command returns a non-zero status, the shell exits. * Corresponds to the -e/--errexit option. */ bool shopt_errexit = false; /* Like `shopt_errexit', but instead of exiting the shell, the "return" * built-in is executed on a non-zero status. */ bool shopt_errreturn = false; /* If set, the last non-zero exit status in a pipeline becomes the exit status * of the whole pipeline. Otherwise, the last command in the pipeline always * defines the exit status of the whole pipeline. Corresponds to the --pipefail * option. */ bool shopt_pipefail = false; /* If set, undefined variables are expanded to an empty string. * Corresponds to the +u/--unset option. */ bool shopt_unset = true; /* If set, commands are actually executed. * Corresponds to the +n/--exec option. */ bool shopt_exec = true; /* If set, the shell doesn't exit when EOF is entered (from a terminal). * Corresponds to the --ignoreeof option. */ bool shopt_ignoreeof = false; /* If set, the shell echos the input to the shell. * Corresponds to the -v/--verbose option. */ bool shopt_verbose = false; /* If set, the shell prints the trace of command execution and assignment. * Corresponds to the -x/--xtrace option. */ bool shopt_xtrace = false; /* If set, the "xtrace" option is not ignored while executing auxiliary * commands. */ bool shopt_traceall = true; #if YASH_ENABLE_HISTORY /* If set, lines that start with a space are not saved in the history. * Corresponds to the --histspace option. */ bool shopt_histspace = false; #endif /* Function definition commands are saved in the history only when this option * is set. * Corresponds to the --log option. * THIS OPTION IS NOT SUPPORTED. */ static bool shopt_log = true; /* If set, the shell performs filename expansion. * Corresponds to the +f/--glob option. */ bool shopt_glob = true; /* If set, filename expansion is case-sensitive. * Corresponds to the --caseglob option. */ bool shopt_caseglob = true; /* If set, in filename expansion, a wildcard character matches a dot at the * beginning of a filename. * Corresponds to the --dotglob option. */ bool shopt_dotglob = false; /* If set, in filename expansion, a directory name is expanded with a slash * appended. * Corresponds to the --markdirs option. */ bool shopt_markdirs = false; /* If set, extended features in filename expansion are enabled. * Corresponds to the --extendedglob option. */ bool shopt_extendedglob = false; /* If set, a glob pattern is removed from the command line rather than left * intact when there are no matches for it. * Corresponds to the --nullglob option. */ bool shopt_nullglob = false; /* If set, brace expansion is enabled. * Corresponds to the --braceexpand option. */ bool shopt_braceexpand = false; /* If set, empty last field is preserved in field splitting. * Corresponds to the --emptylastfield option. */ bool shopt_emptylastfield = false; /* If set, it is allowed to overwrite existing files by redirections. * Corresponds to the +C/--clobber option. */ bool shopt_clobber = true; #if YASH_ENABLE_LINEEDIT /* When line-editing is disabled, `shopt_lineedit' is SHOPT_NOLINEEDIT. * When line-editing is enabled, `shopt_lineedit' is set to another value that * specifies the type of key bindings. * `shopt_vi' and `shopt_emacs' correspond to the --vi and --emacs options, * respectively. `shopt_lineedit' is set according to the values of them. */ enum shopt_lineedit_T shopt_lineedit = SHOPT_NOLINEEDIT; static bool shopt_vi = false, shopt_emacs = false; /* `shopt_le_convmeta' defines treatment of the 8th bit of input characters in * line-editing. The value of `shopt_le_convmeta' is set according to those of * `shopt_le_yesconvmeta' and `shopt_le_noconvmeta', which correspond to the * --le-convmeta and --le-noconvmeta options, respectively. */ enum shopt_yesnoauto_T shopt_le_convmeta = SHOPT_AUTO; static bool shopt_le_yesconvmeta = false, shopt_le_noconvmeta = false; /* If set, line-editing uses a flash (instead of a bell) to alert the user. */ bool shopt_le_visiblebell = false; /* If set, a special character sequence is printed when starting line-editing * to make sure the prompt starts at the beginning of line. */ bool shopt_le_promptsp = true; /* If set, the right prompt is always visible on the screen. */ bool shopt_le_alwaysrp = false; /* If set, auto-suggest the most probable command line as the user enters a * command line. */ bool shopt_le_predict = false; /* If set, auto-suggest also provides suggestions for empty input lines. */ bool shopt_le_predictempty = false; /* If set, debugging information is printed during command completion. */ bool shopt_le_compdebug = false; #endif struct option_T { wchar_t shortopt_enable, shortopt_disable; const wchar_t *longopt; bool *optp; bool resettable; }; /* List of the shell options. */ static const struct option_T shell_options[] = { { L'a', 0, L"allexport", &shopt_allexport, true, }, { 0, 0, L"braceexpand", &shopt_braceexpand, true, }, { 0, 0, L"caseglob", &shopt_caseglob, true, }, { 0, L'C', L"clobber", &shopt_clobber, true, }, { L'c', 0, L"cmdline", &shopt_cmdline, false, }, { 0, 0, L"curasync", &shopt_curasync, true, }, { 0, 0, L"curbg", &shopt_curbg, true, }, { 0, 0, L"curstop", &shopt_curstop, true, }, { 0, 0, L"dotglob", &shopt_dotglob, true, }, #if YASH_ENABLE_LINEEDIT { 0, 0, L"emacs", &shopt_emacs, true, }, #endif { 0, 0, L"emptylastfield", &shopt_emptylastfield, true, }, { L'e', 0, L"errexit", &shopt_errexit, true, }, { 0, 0, L"errreturn", &shopt_errreturn, true, }, { 0, L'n', L"exec", &shopt_exec, true, }, { 0, 0, L"extendedglob", &shopt_extendedglob, true, }, { 0, 0, L"forlocal", &shopt_forlocal, true, }, { 0, L'f', L"glob", &shopt_glob, true, }, { L'h', 0, L"hashondef", &shopt_hashondef, true, }, #if YASH_ENABLE_HISTORY { 0, 0, L"histspace", &shopt_histspace, true, }, #endif { 0, 0, L"ignoreeof", &shopt_ignoreeof, true, }, { L'i', 0, L"interactive", &is_interactive, false, }, #if YASH_ENABLE_LINEEDIT { 0, 0, L"lealwaysrp", &shopt_le_alwaysrp, true, }, { 0, 0, L"lecompdebug", &shopt_le_compdebug, true, }, { 0, 0, L"leconvmeta", &shopt_le_yesconvmeta, true, }, { 0, 0, L"lenoconvmeta", &shopt_le_noconvmeta, true, }, { 0, 0, L"lepredict", &shopt_le_predict, true, }, { 0, 0, L"lepredictempty", &shopt_le_predictempty,true, }, { 0, 0, L"lepromptsp", &shopt_le_promptsp, true, }, { 0, 0, L"levisiblebell", &shopt_le_visiblebell, true, }, #endif { 0, 0, L"log", &shopt_log, true, }, { L'l', 0, L"login", &is_login_shell, false, }, { 0, 0, L"markdirs", &shopt_markdirs, true, }, { L'm', 0, L"monitor", &do_job_control, true, }, { L'b', 0, L"notify", &shopt_notify, true, }, #if YASH_ENABLE_LINEEDIT { 0, 0, L"notifyle", &shopt_notifyle, true, }, #endif { 0, 0, L"nullglob", &shopt_nullglob, true, }, { 0, 0, L"pipefail", &shopt_pipefail, true, }, { 0, 0, L"posixlycorrect", &posixly_correct, true, }, { L's', 0, L"stdin", &shopt_stdin, false, }, { 0, 0, L"traceall", &shopt_traceall, true, }, { 0, L'u', L"unset", &shopt_unset, true, }, { L'v', 0, L"verbose", &shopt_verbose, true, }, #if YASH_ENABLE_LINEEDIT { 0, 0, L"vi", &shopt_vi, true, }, #endif { L'x', 0, L"xtrace", &shopt_xtrace, true, }, { 0, 0, NULL, NULL, false, }, }; enum normal_option_index_T { #if YASH_ENABLE_HELP NOI_HELP, #endif NOI_VERSION, NOI_NOPROFILE, NOI_NORCFILE, NOI_PROFILE, NOI_RCFILE, NOI_N, }; /* List of the normal options. * The normal options are the options that can be used on the shell invocation * but that are not shell options. */ /* A non-NULL `ptr' member indicates that the option can be used for the "set" * built-in. */ static const struct xgetopt_T normal_options[] = { #if YASH_ENABLE_HELP [NOI_HELP] = { L'-', L"help", OPTARG_NONE, false, (void *) &normal_options, }, #endif [NOI_VERSION] = { L'V', L"version", OPTARG_NONE, false, NULL, }, [NOI_NOPROFILE] = { L'-', L"noprofile", OPTARG_NONE, false, NULL, }, [NOI_NORCFILE] = { L'-', L"norcfile", OPTARG_NONE, false, NULL, }, [NOI_PROFILE] = { L'-', L"profile", OPTARG_REQUIRED, false, NULL, }, [NOI_RCFILE] = { L'-', L"rcfile", OPTARG_REQUIRED, false, NULL, }, [NOI_N] = { L'\0', NULL, 0, false, NULL, }, }; /********** Functions **********/ static wchar_t *normalize_option_name(const wchar_t *optname) __attribute__((nonnull,malloc,warn_unused_result)); static int parse_short_option(void *const *argv, bool enable, struct shell_invocation_T *shell_invocation) __attribute__((nonnull(1))); static int parse_option_character( wchar_t opt, bool enable, struct shell_invocation_T *shell_invocation); static int parse_long_option(void *const *argv, bool enable, struct shell_invocation_T *shell_invocation) __attribute__((nonnull(1))); static size_t collect_matching_shell_options( const wchar_t *optstr, plist_T *options) __attribute__((nonnull)); static void search_shell_options(const wchar_t *optname, plist_T *resultlist) __attribute__((nonnull)); static void search_normal_options(const wchar_t *optname, plist_T *resultlist, const struct shell_invocation_T *shell_invocation) __attribute__((nonnull(1,2))); static int handle_search_result(plist_T *options, void *const *argv, bool enable, size_t shelloptindex, size_t noshelloptindex, struct shell_invocation_T *shell_invocation) __attribute__((nonnull)); static int set_shell_option(const struct option_T *option, bool enable, struct shell_invocation_T *shell_invocation) __attribute__((nonnull(1))); #if YASH_ENABLE_LINEEDIT static void update_lineedit_option(void); static void update_le_convmeta_option(void); #endif static int set_normal_option(const struct xgetopt_T *opt, const wchar_t *arg, struct shell_invocation_T *shell_invocation) __attribute__((nonnull(1))); #if YASH_ENABLE_TEST static int test_option_unique(const wchar_t *s) __attribute__((nonnull,pure)); #endif /* Parses the specified command line arguments and accordingly sets the shell * options and positional parameters. * When this function is called from the `main' function of the shell, * `shell_invocation' must be a pointer to a structure to which parse result is * stored. Otherwise, that is, when called from the set built-in, * `shell_invocation' must be NULL. * Positional parameters are set only when `shell_invocation' is NULL. * `xoptind' is set to the index of the first operand. * Returns Exit_SUCCESS if successful. Otherwise, returns Exit_FAILURE or * Exit_ERROR after printing an error message. */ int parse_shell_options(int argc, void *const *argv, struct shell_invocation_T *shell_invocation) { /* We don't use the xgetopt function because of the non-standard syntax. */ for (xoptind = 1; xoptind < argc; xoptind++) { const wchar_t *arg = ARGV(xoptind); bool enable; switch (arg[0]) { case L'-': enable = true; break; case L'+': enable = false; break; default: goto main; } if (arg[1] == L'\0') { if (enable && shell_invocation != NULL) /* ignore first "-" */ xoptind++; goto main; } int result; if (arg[0] == arg[1]) { if (arg[2] == L'\0') { if (enable) /* `arg' is L"--" */ xoptind++; goto set_positional_parameters; } result = parse_long_option(argv, enable, shell_invocation); } else { result = parse_short_option(argv, enable, shell_invocation); } if (result != Exit_SUCCESS) return result; } main: if (xoptind < argc) { set_positional_parameters: assert(xoptind <= argc); if (shell_invocation == NULL) set_positional_parameters(&argv[xoptind]); } return Exit_SUCCESS; } /* Returns a newly malloced string containing the same string as the argument * string except that the returned string does not contain non-alphanumeric * characters. */ wchar_t *normalize_option_name(const wchar_t *optname) { xwcsbuf_T result; wb_init(&result); for (const wchar_t *s = optname; *s != L'\0'; s++) if (iswalnum(*s)) wb_wccat(&result, towlower(*s)); return wb_towcs(&result); } /* Parses the single-character option at `xoptind' in the specified arguments * and enables/disables the option. * Returns Exit_SUCCESS iff successful. Prints an error message on error. */ int parse_short_option(void *const *argv, bool enable, struct shell_invocation_T *shell_invocation) { const wchar_t *optstr = ARGV(xoptind); assert(optstr[0] != L'\0'); for (size_t i = 1; optstr[i] != L'\0'; i++) { if (optstr[i] == L'o') { const wchar_t *optname = &optstr[i + 1]; if (*optname == L'\0') { optname = ARGV(++xoptind);; if (optname == NULL) { xerror(0, Ngt("the -%lc option requires an argument"), (wint_t) L'o'); return special_builtin_error(Exit_ERROR); } } plist_T options; pl_init(&options); size_t shelloptindex = collect_matching_shell_options(optname, &options); return handle_search_result(&options, argv, enable, shelloptindex, options.length, shell_invocation); } else { int result = parse_option_character( optstr[i], enable, shell_invocation); if (result != Exit_SUCCESS) return result; } } return Exit_SUCCESS; } /* Parses the specified single-character option and enables/disables the option. * Returns Exit_SUCCESS iff successful. Prints an error message on error. */ int parse_option_character( wchar_t opt, bool enable, struct shell_invocation_T *shell_invocation) { assert(opt != L'\0'); for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { if (opt == o->shortopt_enable) return set_shell_option(o, enable, shell_invocation); else if (opt == o->shortopt_disable) return set_shell_option(o, !enable, shell_invocation); } if (opt != L'-') { const struct xgetopt_T *o; for (o = normal_options; o->shortopt != L'\0'; o++) { if (!o->posix && posixly_correct) continue; if (opt == o->shortopt && (shell_invocation != NULL || o->ptr != NULL)) return set_normal_option(o, NULL, shell_invocation); } } xerror(0, Ngt("`%ls' is not a valid option"), (wchar_t[]) { opt, L'\0' }); return special_builtin_error(Exit_ERROR); } /* Parses the long option at `xoptind' in the specified arguments and * enables/disables the option. * Returns Exit_SUCCESS iff successful. Prints an error message on error. */ int parse_long_option(void *const *argv, bool enable, struct shell_invocation_T *shell_invocation) { const wchar_t *optstr = ARGV(xoptind); plist_T options; size_t shelloptindex, noshelloptindex; pl_init(&options); if (posixly_correct) { /* No long options are available in the POSIXly correct mode. */ shelloptindex = noshelloptindex = 0; } else { shelloptindex = collect_matching_shell_options(optstr, &options); noshelloptindex = options.length; if (enable) { assert(matchwcsprefix(optstr, L"--")); search_normal_options(&optstr[2], &options, shell_invocation); } } return handle_search_result(&options, argv, enable, shelloptindex, noshelloptindex, shell_invocation); } /* Collects shell options that match the specified name. * This function adds to `options' pointers to shell options (struct option_T) * whose names start with `optname'. * If `optname' starts with "no", options whose names start with `optname' * without "no" are also added. The index of the first such option is returned. */ size_t collect_matching_shell_options(const wchar_t *optname, plist_T *options) { wchar_t *normoptname = normalize_option_name(optname); search_shell_options(normoptname, options); size_t nooptindex = options->length; if (matchwcsprefix(normoptname, L"no")) search_shell_options(&normoptname[2], options); free(normoptname); return nooptindex; } /* Adds pointers to shell options (struct option_T) whose names match `optname' * to the specified list. * If `optname' exactly matches one of the shell option names, `resultlist' is * cleared and only a pointer to that option is added. */ void search_shell_options(const wchar_t *optname, plist_T *resultlist) { for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { const wchar_t *s = matchwcsprefix(o->longopt, optname); if (s != NULL) { if (*s == L'\0') { /* exact match! */ pl_clear(resultlist, 0); pl_add(resultlist, o); break; } else { /* partial match */ pl_add(resultlist, o); } } } } /* Adds to `resultlist' pointers to shell options (struct option_T) whose names * match `optname'. * If `optname' exactly matches one of shell option names, `resultlist' is * cleared and only a pointer to that option is added. */ void search_normal_options(const wchar_t *optname, plist_T *resultlist, const struct shell_invocation_T *shell_invocation) { size_t namelen = wcscspn(optname, L"="); for (const struct xgetopt_T *o = normal_options; o->longopt != NULL; o++) { if (!o->posix && posixly_correct) continue; if (wcsncmp(o->longopt, optname, namelen) == 0 && (shell_invocation != NULL || o->ptr != NULL)) { if (o->longopt[namelen] == L'\0') { /* exact match! */ pl_clear(resultlist, 0); pl_add(resultlist, o); break; } else { /* partial match */ pl_add(resultlist, o); } } } } /* Handles the result of option search. * List `options' must contain pointers to options (struct option_T/xgetopt_T) * that match the currently parsed option name. List members with indices less * than `noshelloptindex' are pointers to shell options (struct option_T) and * the others to normal options (struct xgetopt_T). Inverting shell options, * which have the "no" prefix in their names, must have indices no less than * `shelloptindex'. The list is destroyed in this function. */ int handle_search_result(plist_T *options, void *const *argv, bool enable, size_t shelloptindex, size_t noshelloptindex, struct shell_invocation_T *shell_invocation) { assert(shelloptindex <= noshelloptindex); assert(noshelloptindex <= options->length); const wchar_t *optstr = ARGV(xoptind); switch (options->length) { case 0: pl_destroy(options); xerror(0, Ngt("`%ls' is not a valid option"), optstr); return special_builtin_error(Exit_ERROR); case 1: if (noshelloptindex > 0) { const struct option_T *opt = options->contents[0]; pl_destroy(options); if (shelloptindex == 0) enable = !enable; return set_shell_option(opt, enable, shell_invocation); } else { const struct xgetopt_T *opt = options->contents[0]; const wchar_t *eq = wcschr(optstr, L'='); const wchar_t *optarg; pl_destroy(options); if (opt->optarg != OPTARG_NONE) { if (eq == NULL) { switch (opt->optarg) { case OPTARG_OPTIONAL: optarg = NULL; break; case OPTARG_REQUIRED: optarg = ARGV(++xoptind); if (optarg == NULL) { xerror(0, Ngt("the --%ls option requires " "an argument"), opt->longopt); return special_builtin_error(Exit_ERROR); } break; default: assert(false); } } else { optarg = &eq[1]; } } else { if (eq != NULL) { xerror(0, Ngt("%ls: the --%ls option does not take " "an argument"), optstr, opt->longopt); return special_builtin_error(Exit_ERROR); } optarg = NULL; } return set_normal_option(opt, optarg, shell_invocation); } default: xerror(0, Ngt("option `%ls' is ambiguous"), optstr); #if LIST_AMBIGUOUS_OPTIONS size_t i = 0; for (; i < shelloptindex; i++) fprintf(stderr, "\t--%ls\n", ((const struct option_T *) options->contents[i])->longopt); for (; i < noshelloptindex; i++) fprintf(stderr, "\t--no%ls\n", ((const struct option_T *) options->contents[i])->longopt); for (; i < options->length; i++) fprintf(stderr, "\t--%ls\n", ((const struct xgetopt_T *) options->contents[i])->longopt); #endif pl_destroy(options); return special_builtin_error(Exit_ERROR); } } /* Sets the specified shell option. * Returns Exit_SUCCESS iff successful. Print an error message on error. */ int set_shell_option(const struct option_T *option, bool enable, struct shell_invocation_T *shell_invocation) { if (shell_invocation == NULL && !option->resettable) { xerror(0, Ngt("the %ls option cannot be changed " "once the shell has been initialized"), option->longopt); return special_builtin_error(Exit_ERROR); } *option->optp = enable; if (shell_invocation != NULL) { if (option->optp == &is_interactive) shell_invocation->is_interactive_set = true; else if (option->optp == &do_job_control) shell_invocation->do_job_control_set = true; } if (shell_invocation == NULL && option->optp == &do_job_control) { if (do_job_control && ttyfd < 0) open_ttyfd(); if (do_job_control) ensure_foreground(); reset_job_signals(); } #if YASH_ENABLE_LINEEDIT if (option->optp == &shopt_vi) { if (enable) shopt_emacs = false; update_lineedit_option(); if (shell_invocation != NULL) shell_invocation->lineedit_set = true; } else if (option->optp == &shopt_emacs) { if (enable) shopt_vi = false; update_lineedit_option(); if (shell_invocation != NULL) shell_invocation->lineedit_set = true; } else if (option->optp == &shopt_le_yesconvmeta) { if (enable) shopt_le_noconvmeta = false; update_le_convmeta_option(); } else if (option->optp == &shopt_le_noconvmeta) { if (enable) shopt_le_yesconvmeta = false; update_le_convmeta_option(); } #endif return (*option->optp == enable) ? Exit_SUCCESS : special_builtin_error(Exit_FAILURE); } #if YASH_ENABLE_LINEEDIT /* Updates the value of `shopt_lineedit' according to the values of `shopt_vi' * and `shopt_emacs'. */ void update_lineedit_option(void) { if (shopt_vi) shopt_lineedit = SHOPT_VI; else if (shopt_emacs) shopt_lineedit = SHOPT_EMACS; else shopt_lineedit = SHOPT_NOLINEEDIT; } /* Updates the value of `shopt_le_convmeta' according to the values of * `shopt_le_yesconvmeta' and `shopt_le_noconvmeta'. */ void update_le_convmeta_option(void) { if (shopt_le_yesconvmeta) shopt_le_convmeta = SHOPT_YES; else if (shopt_le_noconvmeta) shopt_le_convmeta = SHOPT_NO; else shopt_le_convmeta = SHOPT_AUTO; } #endif /* Sets the specified normal option. * Returns Exit_SUCCESS iff successful. Print an error message on error. */ int set_normal_option(const struct xgetopt_T *opt, const wchar_t *arg, struct shell_invocation_T *shell_invocation) { enum normal_option_index_T index = opt - normal_options; if (shell_invocation == NULL) { switch (index) { #if YASH_ENABLE_HELP case NOI_HELP: return print_builtin_help(L"set"); #endif default: assert(false); } } else { switch (index) { #if YASH_ENABLE_HELP case NOI_HELP: shell_invocation->help = true; break; #endif case NOI_VERSION: shell_invocation->version = true; break; case NOI_NOPROFILE: shell_invocation->noprofile = true; break; case NOI_NORCFILE: shell_invocation->norcfile = true; break; case NOI_PROFILE: assert(arg != NULL); shell_invocation->profile = arg; break; case NOI_RCFILE: assert(arg != NULL); shell_invocation->rcfile = arg; break; case NOI_N: assert(false); } } return Exit_SUCCESS; } #if YASH_ENABLE_LINEEDIT /* Sets the line-editing option to the specified value. */ void set_lineedit_option(enum shopt_lineedit_T v) { shopt_vi = shopt_emacs = false; shopt_lineedit = v; switch (v) { case SHOPT_VI: shopt_vi = true; break; case SHOPT_EMACS: shopt_emacs = true; break; case SHOPT_NOLINEEDIT: break; } } #endif #if YASH_ENABLE_TEST /* Returns true iff the specified string is a valid option name that can be used * as the argument to the "-o" option. */ bool is_valid_option_name(const wchar_t *s) { return test_option_unique(s); } /* Returns true iff the specified string is a valid option name that can be used * as the argument to the "-o" option and the option is enabled. */ bool option_is_enabled(const wchar_t *s) { return test_option_unique(s) == 2; } /* Tests if the specified string is a valid option name that can be used * as the argument to the "-o" option. Returns: * 0 if the string is not a valid option name, * 1 if it is a valid option that is currently disabled, or * 2 if it is a valid option that is currently enabled. */ int test_option_unique(const wchar_t *s) { plist_T options; pl_init(&options); size_t nooptindex = collect_matching_shell_options(s, &options); int result = (options.length == 1); if (result) { struct option_T *opt = options.contents[0]; bool negated = (nooptindex == 0); result += *opt->optp ^ negated; } pl_destroy(&options); return result; } #endif /* YASH_ENABLE_TEST */ /* Returns the current value of special parameter $- as a newly malloced string. */ wchar_t *get_hyphen_parameter(void) { xwcsbuf_T buf; wb_init(&buf); for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { if (o->shortopt_enable != L'\0' && *o->optp) wb_wccat(&buf, o->shortopt_enable); if (o->shortopt_disable != L'\0' && !*o->optp) wb_wccat(&buf, o->shortopt_disable); } return wb_towcs(&buf); } #if YASH_ENABLE_HELP /* Prints a list of all shell options to the standard output. * Returns true iff successful. */ bool print_shopts_body(bool include_normal_options) { if (include_normal_options) if (!print_option_list(normal_options)) return false; for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { if (!xprintf("\t")) return false; if (o->shortopt_enable != L'\0') { if (!xprintf("-%lc", o->shortopt_enable)) return false; } else if (o->shortopt_disable != L'\0') { if (!xprintf("+%lc", o->shortopt_disable)) return false; } else { if (!xprintf(" ")) return false; } if (!xprintf(" -o %ls\n", o->longopt)) return false; } return true; } #endif /* YASH_ENABLE_HELP */ /********** Built-in **********/ const struct xgetopt_T all_help_options[] = { { L'a', L"all", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* Note: `help_option' is defined as (&all_help_options[1]). */ static int set_builtin_print_current_settings(void); static int set_builtin_print_restoring_commands(void); /* The "set" built-in. */ int set_builtin(int argc, void **argv) { if (argc <= 1) { return typeset_builtin(argc, argv); } else if (argc == 2) { if (wcscmp(ARGV(1), L"-o") == 0) return set_builtin_print_current_settings(); else if (wcscmp(ARGV(1), L"+o") == 0) return set_builtin_print_restoring_commands(); } return parse_shell_options(argc, argv, NULL); } int set_builtin_print_current_settings(void) { const char *vals[] = { [true] = gt("on"), [false] = gt("off"), }; for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { if (!xprintf("%-15ls %s\n", o->longopt, vals[(bool) *o->optp])) return special_builtin_error(Exit_FAILURE); } return Exit_SUCCESS; } int set_builtin_print_restoring_commands(void) { for (const struct option_T *o = shell_options; o->longopt != NULL; o++) { if (o->resettable) if (!xprintf("set %co %ls\n", *o->optp ? '-' : '+', o->longopt)) return special_builtin_error(Exit_FAILURE); } return Exit_SUCCESS; } #if YASH_ENABLE_HELP const char set_help[] = Ngt( "set shell options and positional parameters" ); const char set_syntax[] = Ngt( "\tset [option...] [--] [new_positional_parameter...]\n" "\tset -o|+o # print current settings\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/option.h000066400000000000000000000063721354143602500141100ustar00rootroot00000000000000/* Yash: yet another shell */ /* option.h: option settings */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_OPTION_H #define YASH_OPTION_H #include #include "xgetopt.h" enum shopt_lineedit_T { SHOPT_NOLINEEDIT, SHOPT_VI, SHOPT_EMACS, }; enum shopt_yesnoauto_T { SHOPT_YES, SHOPT_NO, SHOPT_AUTO, }; extern const wchar_t *command_name; extern _Bool posixly_correct; extern _Bool is_login_shell; extern _Bool is_interactive, is_interactive_now; extern _Bool shopt_cmdline, shopt_stdin; extern _Bool do_job_control, shopt_notify, shopt_notifyle, shopt_curasync, shopt_curbg, shopt_curstop; extern _Bool shopt_allexport, shopt_hashondef, shopt_forlocal; extern _Bool shopt_errexit, shopt_errreturn, shopt_pipefail, shopt_unset, shopt_exec, shopt_ignoreeof, shopt_verbose, shopt_xtrace; extern _Bool shopt_traceall; #if YASH_ENABLE_HISTORY extern _Bool shopt_histspace; #endif extern _Bool shopt_glob, shopt_caseglob, shopt_dotglob, shopt_markdirs, shopt_extendedglob, shopt_nullglob; extern _Bool shopt_braceexpand; extern _Bool shopt_emptylastfield; extern _Bool shopt_clobber; #if YASH_ENABLE_LINEEDIT extern enum shopt_lineedit_T shopt_lineedit; extern enum shopt_yesnoauto_T shopt_le_convmeta; extern _Bool shopt_le_visiblebell, shopt_le_promptsp, shopt_le_alwaysrp, shopt_le_predict, shopt_le_predictempty, shopt_le_compdebug; #endif /* Whether or not this shell process is doing job control right now. */ #define doing_job_control_now (do_job_control && ttyfd >= 0) struct shell_invocation_T { _Bool help, version; _Bool noprofile, norcfile; const wchar_t *profile, *rcfile; _Bool is_interactive_set, do_job_control_set, lineedit_set; }; extern int parse_shell_options(int argc, void *const *argv, struct shell_invocation_T *shell_invocation) __attribute__((nonnull(2),warn_unused_result)); #if YASH_ENABLE_LINEEDIT extern void set_lineedit_option(enum shopt_lineedit_T v); #endif extern wchar_t *get_hyphen_parameter(void) __attribute__((malloc,warn_unused_result)); #if YASH_ENABLE_TEST extern _Bool is_valid_option_name(const wchar_t *s) __attribute__((nonnull,pure)); extern _Bool option_is_enabled(const wchar_t *s) __attribute__((nonnull,pure)); #endif #if YASH_ENABLE_HELP extern _Bool print_shopts_body(_Bool include_normal_options); #endif extern const struct xgetopt_T all_help_options[]; #define help_option (&all_help_options[1]) extern int set_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char set_help[], set_syntax[]; #endif #endif /* YASH_OPTION_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/parser.c000066400000000000000000003317151354143602500140710ustar00rootroot00000000000000/* Yash: yet another shell */ /* parser.c: syntax parser */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "parser.h" #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include "alias.h" #include "expand.h" #include "input.h" #include "option.h" #include "plist.h" #include "strbuf.h" #include "util.h" #if YASH_ENABLE_DOUBLE_BRACKET # include "builtins/test.h" #endif #if YASH_ENABLE_LINEEDIT # include "lineedit/lineedit.h" #endif /********** Functions That Free Parse Trees **********/ static void pipesfree(pipeline_T *p); static void ifcmdsfree(ifcommand_T *i); static void caseitemsfree(caseitem_T *i); #if YASH_ENABLE_DOUBLE_BRACKET static void dbexpfree(dbexp_T *e); #endif static void wordunitfree(wordunit_T *wu) __attribute__((nonnull)); static void wordfree_vp(void *w); static void assignsfree(assign_T *a); static void redirsfree(redir_T *r); static void embedcmdfree(embedcmd_T c); void andorsfree(and_or_T *a) { while (a != NULL) { pipesfree(a->ao_pipelines); and_or_T *next = a->next; free(a); a = next; } } void pipesfree(pipeline_T *p) { while (p != NULL) { comsfree(p->pl_commands); pipeline_T *next = p->next; free(p); p = next; } } void comsfree(command_T *c) { while (c != NULL) { if (!refcount_decrement(&c->refcount)) break; redirsfree(c->c_redirs); switch (c->c_type) { case CT_SIMPLE: assignsfree(c->c_assigns); plfree(c->c_words, wordfree_vp); break; case CT_GROUP: case CT_SUBSHELL: andorsfree(c->c_subcmds); break; case CT_IF: ifcmdsfree(c->c_ifcmds); break; case CT_FOR: free(c->c_forname); plfree(c->c_forwords, wordfree_vp); andorsfree(c->c_forcmds); break; case CT_WHILE: andorsfree(c->c_whlcond); andorsfree(c->c_whlcmds); break; case CT_CASE: wordfree(c->c_casword); caseitemsfree(c->c_casitems); break; #if YASH_ENABLE_DOUBLE_BRACKET case CT_BRACKET: dbexpfree(c->c_dbexp); break; #endif /* YASH_ENABLE_DOUBLE_BRACKET */ case CT_FUNCDEF: wordfree(c->c_funcname); comsfree(c->c_funcbody); break; } command_T *next = c->next; free(c); c = next; } } void ifcmdsfree(ifcommand_T *i) { while (i != NULL) { andorsfree(i->ic_condition); andorsfree(i->ic_commands); ifcommand_T *next = i->next; free(i); i = next; } } void caseitemsfree(caseitem_T *i) { while (i != NULL) { plfree(i->ci_patterns, wordfree_vp); andorsfree(i->ci_commands); caseitem_T *next = i->next; free(i); i = next; } } #if YASH_ENABLE_DOUBLE_BRACKET void dbexpfree(dbexp_T *e) { if (e == NULL) return; free(e->operator); switch (e->type) { case DBE_OR: case DBE_AND: case DBE_NOT: dbexpfree(e->lhs.subexp); dbexpfree(e->rhs.subexp); break; case DBE_UNARY: case DBE_BINARY: case DBE_STRING: wordfree(e->lhs.word); wordfree(e->rhs.word); break; } free(e); } #endif /* YASH_ENABLE_DOUBLE_BRACKET */ void wordunitfree(wordunit_T *wu) { switch (wu->wu_type) { case WT_STRING: free(wu->wu_string); break; case WT_PARAM: paramfree(wu->wu_param); break; case WT_CMDSUB: embedcmdfree(wu->wu_cmdsub); break; case WT_ARITH: wordfree(wu->wu_arith); break; } free(wu); } void wordfree(wordunit_T *w) { while (w != NULL) { wordunit_T *next = w->next; wordunitfree(w); w = next; } } void wordfree_vp(void *w) { wordfree((wordunit_T *) w); } void paramfree(paramexp_T *p) { if (p != NULL) { if (p->pe_type & PT_NEST) wordfree(p->pe_nest); else free(p->pe_name); wordfree(p->pe_start); wordfree(p->pe_end); wordfree(p->pe_match); wordfree(p->pe_subst); free(p); } } void assignsfree(assign_T *a) { while (a != NULL) { free(a->a_name); switch (a->a_type) { case A_SCALAR: wordfree(a->a_scalar); break; case A_ARRAY: plfree(a->a_array, wordfree_vp); break; } assign_T *next = a->next; free(a); a = next; } } void redirsfree(redir_T *r) { while (r != NULL) { switch (r->rd_type) { case RT_INPUT: case RT_OUTPUT: case RT_CLOBBER: case RT_APPEND: case RT_INOUT: case RT_DUPIN: case RT_DUPOUT: case RT_PIPE: case RT_HERESTR: wordfree(r->rd_filename); break; case RT_HERE: case RT_HERERT: free(r->rd_hereend); wordfree(r->rd_herecontent); break; case RT_PROCIN: case RT_PROCOUT: embedcmdfree(r->rd_command); break; } redir_T *next = r->next; free(r); r = next; } } void embedcmdfree(embedcmd_T c) { if (c.is_preparsed) andorsfree(c.value.preparsed); else free(c.value.unparsed); } /********** Auxiliary Functions for Parser **********/ typedef enum tokentype_T { TT_UNKNOWN, TT_END_OF_INPUT, TT_WORD, TT_IO_NUMBER, /* operators */ TT_NEWLINE, TT_AMP, TT_AMPAMP, TT_LPAREN, TT_RPAREN, TT_SEMICOLON, TT_DOUBLE_SEMICOLON, TT_PIPE, TT_PIPEPIPE, TT_LESS, TT_LESSLESS, TT_LESSAMP, TT_LESSLESSDASH, TT_LESSLESSLESS, TT_LESSGREATER, TT_LESSLPAREN, TT_GREATER, TT_GREATERGREATER, TT_GREATERGREATERPIPE, TT_GREATERPIPE, TT_GREATERAMP, TT_GREATERLPAREN, /* reserved words */ TT_IF, TT_THEN, TT_ELSE, TT_ELIF, TT_FI, TT_DO, TT_DONE, TT_CASE, TT_ESAC, TT_WHILE, TT_UNTIL, TT_FOR, TT_LBRACE, TT_RBRACE, TT_BANG, TT_IN, TT_FUNCTION, #if YASH_ENABLE_DOUBLE_BRACKET TT_DOUBLE_LBRACKET, #endif } tokentype_T; static wchar_t *skip_name(const wchar_t *s, bool predicate(wchar_t)) __attribute__((pure,nonnull)); static bool is_name_by_predicate(const wchar_t *s, bool predicate(wchar_t)) __attribute__((pure,nonnull)); static bool is_portable_name(const wchar_t *s) __attribute__((pure,nonnull)); static tokentype_T identify_reserved_word_string(const wchar_t *s) __attribute__((pure,nonnull)); static bool is_single_string_word(const wordunit_T *wu) __attribute__((pure)); static bool is_digits_only(const wordunit_T *wu) __attribute__((pure)); static bool is_name_word(const wordunit_T *wu) __attribute__((pure)); static tokentype_T identify_reserved_word(const wordunit_T *wu) __attribute__((pure)); static bool is_closing_tokentype(tokentype_T tt) __attribute__((const)); /* Checks if the specified character can be used in a portable variable name. * Returns true for a digit. */ bool is_portable_name_char(wchar_t c) { switch (c) { case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'8': case L'9': case L'a': case L'b': case L'c': case L'd': case L'e': case L'f': case L'g': case L'h': case L'i': case L'j': case L'k': case L'l': case L'm': case L'n': case L'o': case L'p': case L'q': case L'r': case L's': case L't': case L'u': case L'v': case L'w': case L'x': case L'y': case L'z': case L'A': case L'B': case L'C': case L'D': case L'E': case L'F': case L'G': case L'H': case L'I': case L'J': case L'K': case L'L': case L'M': case L'N': case L'O': case L'P': case L'Q': case L'R': case L'S': case L'T': case L'U': case L'V': case L'W': case L'X': case L'Y': case L'Z': case L'_': return true; default: return false; } } /* Checks if the specified character can be used in a variable name. * Returns true for a digit. */ bool is_name_char(wchar_t c) { return c == L'_' || iswalnum(c); } /* Skips an identifier at the head of the specified string and returns a * pointer to the character right after the identifier in the string. * If there is no identifier, the argument `s' is simply returned. `predicate` * determines if a character is valid. */ wchar_t *skip_name(const wchar_t *s, bool predicate(wchar_t)) { if (!iswdigit(*s)) while (predicate(*s)) s++; return (wchar_t *) s; } /* Returns true iff the specified string constitutes a valid identifier that * is made up of characters accepted by `predicate'. */ bool is_name_by_predicate(const wchar_t *s, bool predicate(wchar_t)) { return s[0] != L'\0' && skip_name(s, predicate)[0] == L'\0'; } /* Returns true iff the specified string constitutes a valid portable name. */ bool is_portable_name(const wchar_t *s) { return is_name_by_predicate(s, is_portable_name_char); } /* Returns true iff the specified string constitutes a valid identifier. */ bool is_name(const wchar_t *s) { return is_name_by_predicate(s, is_name_char); } /* Converts a string to the corresponding token type. Returns TT_WORD for * non-reserved words. */ tokentype_T identify_reserved_word_string(const wchar_t *s) { /* List of keywords: * case do done elif else esac fi for function if in then until while * { } [[ ! * The following words are currently not keywords: * select ]] */ switch (s[0]) { case L'c': if (s[1] == L'a' && s[2] == L's' && s[3] == L'e' && s[4]== L'\0') return TT_CASE; break; case L'd': if (s[1] == L'o') { if (s[2] == L'\0') return TT_DO; if (s[2] == L'n' && s[3] == L'e' && s[4] == L'\0') return TT_DONE; } break; case L'e': if (s[1] == L'l') { if (s[2] == L's' && s[3] == L'e' && s[4] == L'\0') return TT_ELSE; if (s[2] == L'i' && s[3] == L'f' && s[4] == L'\0') return TT_ELIF; } if (s[1] == L's' && s[2] == L'a' && s[3] == L'c' && s[4] == L'\0') return TT_ESAC; break; case L'f': if (s[1] == L'i' && s[2] == L'\0') return TT_FI; if (s[1] == L'o' && s[2] == L'r' && s[3] == L'\0') return TT_FOR; if (s[1] == L'u' && s[2] == L'n' && s[3] == L'c' && s[4] == L't' && s[5] == L'i' && s[6] == L'o' && s[7] == L'n' && s[8] == L'\0') return TT_FUNCTION; break; case L'i': if (s[1] == L'f' && s[2] == L'\0') return TT_IF; if (s[1] == L'n' && s[2] == L'\0') return TT_IN; break; case L't': if (s[1] == L'h' && s[2] == L'e' && s[3] == L'n' && s[4]== L'\0') return TT_THEN; break; case L'u': if (s[1] == L'n' && s[2] == L't' && s[3] == L'i' && s[4] == L'l' && s[5] == L'\0') return TT_UNTIL; break; case L'w': if (s[1] == L'h' && s[2] == L'i' && s[3] == L'l' && s[4] == L'e' && s[5] == L'\0') return TT_WHILE; break; case L'{': if (s[1] == L'\0') return TT_LBRACE; break; case L'}': if (s[1] == L'\0') return TT_RBRACE; break; #if YASH_ENABLE_DOUBLE_BRACKET case L'[': if (s[1] == L'[' && s[2] == L'\0') return TT_DOUBLE_LBRACKET; break; #endif /* YASH_ENABLE_DOUBLE_BRACKET */ case L'!': if (s[1] == L'\0') return TT_BANG; break; } return TT_WORD; } /* Returns true iff the string is a reserved word. */ bool is_keyword(const wchar_t *s) { return identify_reserved_word_string(s) != TT_WORD; } bool is_single_string_word(const wordunit_T *wu) { return wu != NULL && wu->next == NULL && wu->wu_type == WT_STRING; } /* Tests if a word is made up of digits only. */ bool is_digits_only(const wordunit_T *wu) { if (!is_single_string_word(wu)) return false; const wchar_t *s = wu->wu_string; assert(s[0] != L'\0'); while (iswdigit(*s)) s++; return *s == L'\0'; } bool is_name_word(const wordunit_T *wu) { if (!is_single_string_word(wu)) return false; return (posixly_correct ? is_portable_name : is_name)(wu->wu_string); } /* Converts a word to the corresponding token type. Returns TT_WORD for * non-reserved words. */ tokentype_T identify_reserved_word(const wordunit_T *wu) { if (!is_single_string_word(wu)) return TT_WORD; return identify_reserved_word_string(wu->wu_string); } /* Determines if the specified token is a 'closing' token such as ")", "}", and * "fi". Closing tokens delimit and/or lists. */ bool is_closing_tokentype(tokentype_T tt) { switch (tt) { case TT_RPAREN: case TT_RBRACE: case TT_THEN: case TT_ELIF: case TT_ELSE: case TT_FI: case TT_DO: case TT_DONE: case TT_DOUBLE_SEMICOLON: case TT_ESAC: return true; default: return false; } } /********** Parser **********/ /* Holds data that are used in parsing. */ typedef struct parsestate_T { /* contains parameters that affect the behavior of parsing */ parseparam_T *info; /* true iff any parsing error occurred */ bool error; /* the source code being parsed */ struct xwcsbuf_T src; /* the position of the current character or `token' */ size_t index; /* the index just past the current `token' */ size_t next_index; /* type of the current `token' */ tokentype_T tokentype; /* the current token (NULL when `tokentype' is an operator token) */ wordunit_T *token; /* here-documents whose contents have not been read */ struct plist_T pending_heredocs; /* false when alias substitution is suppressed */ bool enable_alias; /* true when non-global alias substitution has occurred */ bool reparse; /* record of alias substitutions that are responsible for the current * `index' */ struct aliaslist_T *aliases; } parsestate_T; static void serror(parsestate_T *restrict ps, const char *restrict format, ...) __attribute__((nonnull(1,2),format(printf,2,3))); static void print_errmsg_token(parsestate_T *ps, const char *message) __attribute__((nonnull)); static const char *get_errmsg_unexpected_tokentype(tokentype_T tokentype) __attribute__((const)); static void print_errmsg_token_unexpected(parsestate_T *ps) __attribute__((nonnull)); static void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t) __attribute__((nonnull)); static inputresult_T read_more_input(parsestate_T *ps) __attribute__((nonnull)); static void line_continuation(parsestate_T *ps, size_t index) __attribute__((nonnull)); static void maybe_line_continuations(parsestate_T *ps, size_t index) __attribute__((nonnull)); static void rewind_index(parsestate_T *ps, size_t to) __attribute__((nonnull)); static size_t count_name_length(parsestate_T *ps, bool isnamechar(wchar_t c)) __attribute__((nonnull)); static void next_token(parsestate_T *ps) __attribute__((nonnull)); static wordunit_T *parse_word(parsestate_T *ps, bool testfunc(wchar_t c)) __attribute__((nonnull,malloc,warn_unused_result)); static void skip_to_next_single_quote(parsestate_T *ps) __attribute__((nonnull)); static wordunit_T *parse_special_word_unit(parsestate_T *ps, bool indq) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *tryparse_paramexp_raw(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *parse_paramexp_in_brace(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *parse_cmdsubst_in_paren(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static embedcmd_T extract_command_in_paren(parsestate_T *ps) __attribute__((nonnull,warn_unused_result)); static wchar_t *extract_command_in_paren_unparsed(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *parse_cmdsubst_in_backquote(parsestate_T *ps, bool bsbq) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *tryparse_arith(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static void next_line(parsestate_T *ps) __attribute__((nonnull)); static bool parse_newline_list(parsestate_T *ps) __attribute__((nonnull)); static bool is_comma_or_closing_bracket(wchar_t c) __attribute__((const)); static bool is_slash_or_closing_brace(wchar_t c) __attribute__((const)); static bool is_closing_brace(wchar_t c) __attribute__((const)); static bool psubstitute_alias(parsestate_T *ps, substaliasflags_T f) __attribute__((nonnull)); static void psubstitute_alias_recursive(parsestate_T *ps, substaliasflags_T f) __attribute__((nonnull)); static and_or_T *parse_command_list(parsestate_T *ps, bool toeol) __attribute__((nonnull,malloc,warn_unused_result)); static and_or_T *parse_compound_list(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static and_or_T *parse_and_or_list(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static pipeline_T *parse_pipelines_in_and_or(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static pipeline_T *parse_pipeline(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_commands_in_pipeline(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_command(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static void **parse_simple_command_tokens( parsestate_T *ps, assign_T **assigns, redir_T **redirs) __attribute__((nonnull,malloc,warn_unused_result)); static void **parse_words(parsestate_T *ps, bool skip_newlines) __attribute__((nonnull,malloc,warn_unused_result)); static void parse_redirect_list(parsestate_T *ps, redir_T **lastp) __attribute__((nonnull)); static assign_T *tryparse_assignment(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static redir_T *tryparse_redirect(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static void validate_redir_operand(parsestate_T *ps) __attribute__((nonnull)); static command_T *parse_compound_command(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_group(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_if(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_for(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_while(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *parse_case(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static caseitem_T *parse_case_list(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static void **parse_case_patterns(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); #if YASH_ENABLE_DOUBLE_BRACKET static command_T *parse_double_bracket(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static dbexp_T *parse_double_bracket_ors(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static dbexp_T *parse_double_bracket_ands(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static dbexp_T *parse_double_bracket_nots(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static dbexp_T *parse_double_bracket_primary(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static wordunit_T *parse_double_bracket_operand(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); #endif /* YASH_ENABLE_DOUBLE_BRACKET */ static command_T *parse_function(parsestate_T *ps) __attribute__((nonnull,malloc,warn_unused_result)); static command_T *try_reparse_as_function(parsestate_T *ps, command_T *c) __attribute__((nonnull,warn_unused_result)); static void read_heredoc_contents(parsestate_T *ps, redir_T *redir) __attribute__((nonnull)); static void read_heredoc_contents_without_expansion( parsestate_T *ps, redir_T *r) __attribute__((nonnull)); static void read_heredoc_contents_with_expansion(parsestate_T *ps, redir_T *r) __attribute__((nonnull)); static bool is_end_of_heredoc_contents( parsestate_T *ps, const wchar_t *eoc, bool skiptab) __attribute__((nonnull)); static void reject_pending_heredocs(parsestate_T *ps) __attribute__((nonnull)); static wordunit_T **parse_string_without_quotes( parsestate_T *ps, bool backquote, bool stoponnewline, wordunit_T **lastp) __attribute__((nonnull)); #define QUOTES L"\"'\\" /***** Entry points *****/ /* The functions below may return non-NULL even on error. * The error condition must be tested by the `error' flag of the parsestate_T * structure. It is set to true when `serror' is called. */ /* Every function named `parse_*' advances the current position (the `index' * value of the parsestate_T structure) to the index of the first character * that has not yet been parsed. Syntax parser functions also update the * current `token' and `tokentype' to the first unconsumed token, in which * case `index' points to the first character of the `token'. */ /* The main entry point to the parser. * This function reads at least one line of input and parses it. * All the members of `info' except `lastinputresult' must have been initialized * beforehand. * The resulting parse tree is assigned to `*resultp' if successful. If there is * no command in the next line or the shell was interrupted while reading input, * `*resultp' is assigned NULL. * Returns PR_OK if successful, * PR_SYNTAX_ERROR if a syntax error occurred, * PR_INPUT_ERROR if an input error occurred, or * PR_EOF if the input reached the end of file (EOF). * If PR_SYNTAX_ERROR or PR_INPUT_ERROR is returned, at least one error message * has been printed in this function. * Note that `*resultp' is assigned if and only if the return value is PR_OK. */ parseresult_T read_and_parse(parseparam_T *info, and_or_T **restrict resultp) { parsestate_T ps = { .info = info, .error = false, .index = 0, .next_index = 0, .tokentype = TT_UNKNOWN, .token = NULL, .enable_alias = info->enable_alias, .reparse = false, .aliases = NULL, }; if (ps.info->interactive) { struct input_interactive_info_T *intrinfo = ps.info->inputinfo; intrinfo->prompttype = 1; } ps.info->lastinputresult = INPUT_OK; wb_init(&ps.src); pl_init(&ps.pending_heredocs); and_or_T *r = parse_command_list(&ps, true); reject_pending_heredocs(&ps); wb_destroy(&ps.src); pl_destroy(&ps.pending_heredocs); destroy_aliaslist(ps.aliases); wordfree(ps.token); switch (ps.info->lastinputresult) { case INPUT_OK: case INPUT_EOF: if (ps.error) { andorsfree(r); return PR_SYNTAX_ERROR; } else if (ps.src.length == 0) { andorsfree(r); return PR_EOF; } else { assert(ps.index == ps.src.length); *resultp = r; return PR_OK; } case INPUT_INTERRUPTED: andorsfree(r); *resultp = NULL; return PR_OK; case INPUT_ERROR: andorsfree(r); return PR_INPUT_ERROR; } assert(false); } /* Parses a string recognizing parameter expansions, command substitutions of * the form "$(...)" and arithmetic expansions. * All the members of `info' except `lastinputresult' must have been initialized * beforehand. * This function reads and parses the input to the end of file. * Iff successful, the result is assigned to `*resultp' and true is returned. * If the input is empty, NULL is assigned. * On error, the value of `*resultp' is undefined. */ bool parse_string(parseparam_T *info, wordunit_T **restrict resultp) { parsestate_T ps = { .info = info, .error = false, .index = 0, .next_index = 0, .tokentype = TT_UNKNOWN, .token = NULL, .enable_alias = false, .reparse = false, .aliases = NULL, }; wb_init(&ps.src); ps.info->lastinputresult = INPUT_OK; read_more_input(&ps); pl_init(&ps.pending_heredocs); resultp = parse_string_without_quotes(&ps, false, false, resultp); *resultp = NULL; wb_destroy(&ps.src); pl_destroy(&ps.pending_heredocs); assert(ps.aliases == NULL); //destroy_aliaslist(ps.aliases); wordfree(ps.token); if (ps.info->lastinputresult != INPUT_EOF || ps.error) { wordfree(*resultp); return false; } else { return true; } } /***** Error message utility *****/ /* Prints the specified error message to the standard error. * `format' is passed to `gettext' in this function. * `format' need not to have a trailing newline since a newline is automatically * appended in this function. * The `ps->error' flag is set to true in this function. */ void serror(parsestate_T *restrict ps, const char *restrict format, ...) { va_list ap; if (ps->info->print_errmsg && ps->info->lastinputresult != INPUT_INTERRUPTED) { if (ps->info->filename != NULL) fprintf(stderr, "%s:%lu: ", ps->info->filename, ps->info->lineno); fprintf(stderr, gt("syntax error: ")); va_start(ap, format); vfprintf(stderr, gt(format), ap); va_end(ap); fputc('\n', stderr); fflush(stderr); } ps->error = true; } void print_errmsg_token(parsestate_T *ps, const char *message) { assert(ps->index <= ps->next_index); size_t length = ps->next_index - ps->index; wchar_t token[length + 1]; wcsncpy(token, &ps->src.contents[ps->index], length); token[length] = L'\0'; serror(ps, message, token); } const char *get_errmsg_unexpected_tokentype(tokentype_T tokentype) { switch (tokentype) { case TT_RPAREN: return Ngt("encountered `%ls' without a matching `('"); case TT_RBRACE: return Ngt("encountered `%ls' without a matching `{'"); case TT_DOUBLE_SEMICOLON: return Ngt("`%ls' is used outside `case'"); case TT_BANG: return Ngt("`%ls' cannot be used as a command name"); case TT_IN: return Ngt("`%ls' cannot be used as a command name"); case TT_FI: return Ngt("encountered `%ls' " "without a matching `if' and/or `then'"); case TT_THEN: return Ngt("encountered `%ls' without a matching `if' or `elif'"); case TT_DO: return Ngt("encountered `%ls' " "without a matching `for', `while', or `until'"); case TT_DONE: return Ngt("encountered `%ls' without a matching `do'"); case TT_ESAC: return Ngt("encountered `%ls' without a matching `case'"); case TT_ELIF: case TT_ELSE: return Ngt("encountered `%ls' " "without a matching `if' and/or `then'"); default: assert(false); } } void print_errmsg_token_unexpected(parsestate_T *ps) { print_errmsg_token(ps, get_errmsg_unexpected_tokentype(ps->tokentype)); } void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t) { if (is_closing_tokentype(ps->tokentype)) { print_errmsg_token_unexpected(ps); serror(ps, Ngt("(maybe you missed `%ls'?)"), t); } else { serror(ps, Ngt("`%ls' is missing"), t); } } /***** Input buffer manipulators *****/ /* Reads the next line of input and returns the result type, which is assigned * to `ps->info->lastinputresult'. * If `ps->info->lastinputresult' is not INPUT_OK, it is simply returned * without reading any input. * If input is from an interactive terminal and `ps->error' is true, no input * is read and INPUT_INTERRUPTED is returned. */ inputresult_T read_more_input(parsestate_T *ps) { if (ps->error && ps->info->interactive) return INPUT_INTERRUPTED; if (ps->info->lastinputresult == INPUT_OK) { size_t savelength = ps->src.length; ps->info->lastinputresult = ps->info->input(&ps->src, ps->info->inputinfo); if (ps->info->enable_verbose && shopt_verbose) #if YASH_ENABLE_LINEEDIT if (!(le_state & LE_STATE_ACTIVE)) #endif fprintf(stderr, "%ls", &ps->src.contents[savelength]); } return ps->info->lastinputresult; } /* Removes a line continuation at the specified index in `ps->src', increments * line number, and reads the next line. */ void line_continuation(parsestate_T *ps, size_t index) { assert(ps->src.contents[index] == L'\\' && ps->src.contents[index + 1] == L'\n'); wb_remove(&ps->src, index, 2); shift_aliaslist_index(ps->aliases, index + 1, -2); ps->info->lineno++; if (ps->src.contents[index] == L'\0') read_more_input(ps); } /* Removes line continuations at the specified index. * The next line will be read if the removed line continuation is at the end of * the buffer. */ void maybe_line_continuations(parsestate_T *ps, size_t index) { assert(index <= ps->src.length); if (index == ps->src.length) read_more_input(ps); while (ps->src.contents[index] == L'\\' && ps->src.contents[index + 1] == L'\n') line_continuation(ps, index); } /* Rewind `ps->index` to `oldindex' and decrease `ps->info->lineno' accordingly. * Note that `ps->next_index' is not updated in this function. * * You MUST use this function when rewinding the index in order to correctly * rewind the line number. The following pattern of code does not work because * it does not account for line continuations that have been removed from * `ps->src'. * * size_t oldindex = ps->index; * unsigned long oldlineno = ps->info->lineno; * * do_something_incrementing_index_and_lineno(ps); * * ps->index = oldindex; * ps->info->lineno = oldlineno; * */ void rewind_index(parsestate_T *ps, size_t oldindex) { while (oldindex < ps->index) { ps->index--; assert(ps->index < ps->src.length); if (ps->src.contents[ps->index] == L'\n') ps->info->lineno--; } } /* Returns the length of the name at the current position. * Whether a character can be part of the name is determined by `isnamechar'. * This function processes line continuations and reads so many lines that the * variable/alias name under the current position is fully available. */ size_t count_name_length(parsestate_T *ps, bool isnamechar(wchar_t c)) { size_t index = ps->index; while (maybe_line_continuations(ps, index), isnamechar(ps->src.contents[index])) index++; return index - ps->index; } /***** Tokenizer *****/ /* Moves to the next token, updating `index', `next_index', `tokentype', and * `token' of the parse state. * The existing `token' is freed. */ void next_token(parsestate_T *ps) { wordfree(ps->token); ps->token = NULL; size_t index = ps->next_index; if (index == ps->src.length) read_more_input(ps); skip_blanks: while (iswblank(ps->src.contents[index])) index++; if (ps->src.contents[index] == L'\\' && ps->src.contents[index + 1] == L'\n') { line_continuation(ps, index); goto skip_blanks; } /* skip any comment */ if (ps->src.contents[index] == L'#') index += wcscspn(&ps->src.contents[index + 1], L"\n") + 1; size_t startindex = index; switch (ps->src.contents[index]) { case L'\0': ps->tokentype = TT_END_OF_INPUT; break; case L'\n': ps->tokentype = TT_NEWLINE; index++; break; case L'(': ps->tokentype = TT_LPAREN; index++; break; case L')': ps->tokentype = TT_RPAREN; index++; break; case L';': maybe_line_continuations(ps, ++index); if (ps->src.contents[index] == L';') { ps->tokentype = TT_DOUBLE_SEMICOLON; index++; } else { ps->tokentype = TT_SEMICOLON; } break; case L'&': maybe_line_continuations(ps, ++index); if (ps->src.contents[index] == L'&') { ps->tokentype = TT_AMPAMP; index++; } else { ps->tokentype = TT_AMP; } break; case L'|': maybe_line_continuations(ps, ++index); if (ps->src.contents[index] == L'|') { ps->tokentype = TT_PIPEPIPE; index++; } else { ps->tokentype = TT_PIPE; } break; case L'<': maybe_line_continuations(ps, ++index); switch (ps->src.contents[index]) { default: ps->tokentype = TT_LESS; break; case L'>': ps->tokentype = TT_LESSGREATER; index++; break; case L'(': ps->tokentype = TT_LESSLPAREN; index++; break; case L'&': ps->tokentype = TT_LESSAMP; index++; break; case L'<': maybe_line_continuations(ps, ++index); switch (ps->src.contents[index]) { default: ps->tokentype = TT_LESSLESS; break; case L'-': ps->tokentype = TT_LESSLESSDASH; index++; break; case L'<': ps->tokentype = TT_LESSLESSLESS; index++; break; } break; } break; case L'>': maybe_line_continuations(ps, ++index); switch (ps->src.contents[index]) { default: ps->tokentype = TT_GREATER; break; case L'(': ps->tokentype = TT_GREATERLPAREN; index++; break; case L'&': ps->tokentype = TT_GREATERAMP; index++; break; case L'|': ps->tokentype = TT_GREATERPIPE; index++; break; case L'>': maybe_line_continuations(ps, ++index); if (ps->src.contents[index] == L'|') { ps->tokentype = TT_GREATERGREATERPIPE; index++; } else { ps->tokentype = TT_GREATERGREATER; } break; } break; default: /* Okay, the next token seems to be a word, possibly being a * reserved word or an IO_NUMBER token. */ ps->index = index; wordunit_T *token = parse_word(ps, is_token_delimiter_char); index = ps->index; wordfree(ps->token); ps->token = token; /* Is this an IO_NUMBER token? */ if (ps->src.contents[index] == L'<' || ps->src.contents[index] == L'>') { if (is_digits_only(ps->token)) { ps->tokentype = TT_IO_NUMBER; break; } } /* Is this a reserved word? */ ps->tokentype = identify_reserved_word(ps->token); break; } ps->index = startindex; ps->next_index = index; } /* Parses a word at the current position. * `testfunc' is a function that determines if a character is a word delimiter. * The parsing proceeds up to an unescaped character for which `testfunc' * returns false. * It is not an error if there is no characters to be a word, in which case * NULL is returned. */ wordunit_T *parse_word(parsestate_T *ps, bool testfunc(wchar_t c)) { wordunit_T *first = NULL, **lastp = &first; bool indq = false; /* in double quotes? */ size_t startindex = ps->index; /* appends the substring from `startindex' to `index' as a new word unit * to `*lastp' */ #define MAKE_WORDUNIT_STRING \ do { \ size_t len = ps->index - startindex; \ if (len > 0) { \ wordunit_T *w = xmalloc(sizeof *w); \ w->next = NULL; \ w->wu_type = WT_STRING; \ w->wu_string = xwcsndup(&ps->src.contents[startindex], len); \ *lastp = w; \ lastp = &w->next; \ } \ } while (0) while (maybe_line_continuations(ps, ps->index), indq || !testfunc(ps->src.contents[ps->index])) { switch (ps->src.contents[ps->index]) { case L'\0': goto done; // reached EOF case L'\\': if (ps->src.contents[ps->index + 1] != L'\0') { assert(ps->src.contents[ps->index + 1] != L'\n'); ps->index += 2; continue; } break; case L'\n': ps->info->lineno++; break; case L'$': case L'`': MAKE_WORDUNIT_STRING; wordunit_T *wu = parse_special_word_unit(ps, indq); startindex = ps->index; if (wu != NULL) { *lastp = wu; lastp = &wu->next; continue; } else if (ps->src.contents[ps->index] == L'\0') { continue; } break; case L'\'': if (!indq) { ps->index++; skip_to_next_single_quote(ps); if (ps->src.contents[ps->index] == L'\'') ps->index++; continue; } break; case L'"': indq = !indq; /* falls thru! */ default: break; } ps->index++; } done: MAKE_WORDUNIT_STRING; if (indq) serror(ps, Ngt("the double quotation is not closed")); return first; } /* Skips to the next single quote. * If the current position is already at a single quote, the position is not * moved. * It is an error if there is no single quote before the end of file. */ void skip_to_next_single_quote(parsestate_T *ps) { for (;;) { switch (ps->src.contents[ps->index]) { case L'\'': return; case L'\0': if (read_more_input(ps) != INPUT_OK) { serror(ps, Ngt("the single quotation is not closed")); return; } continue; case L'\n': ps->info->lineno++; break; default: break; } ps->index++; } } /* Parses a parameter expansion or command substitution that starts with '$' or * '`'. The character at the current position must be '$' or '`' when this * function is called and the position is advanced to right after the expansion * or substitution. * If the character at the current position is '$' but it is not an expansion, * the position is not moved and the return value is NULL. Otherwise, The * position is advanced by at least one character. * Between double quotes, `indq' must be true. */ wordunit_T *parse_special_word_unit(parsestate_T *ps, bool indq) { switch (ps->src.contents[ps->index++]) { case L'$': maybe_line_continuations(ps, ps->index); switch (ps->src.contents[ps->index]) { case L'{': return parse_paramexp_in_brace(ps); case L'(': maybe_line_continuations(ps, ps->index + 1); if (ps->src.contents[ps->index + 1] == L'(') { wordunit_T *wu = tryparse_arith(ps); if (wu != NULL) return wu; } ps->next_index = ps->index + 1; return parse_cmdsubst_in_paren(ps); default: return tryparse_paramexp_raw(ps); } case L'`': return parse_cmdsubst_in_backquote(ps, indq); default: assert(false); } } /* Parses a parameter that is not enclosed by { }. * The current position must be at the first character of the parameter name * that follows L'$'. The position is advanced to right after the name. * If there is no parameter, the position is put back to L'$'. */ wordunit_T *tryparse_paramexp_raw(parsestate_T *ps) { size_t namelen; /* parameter name length */ maybe_line_continuations(ps, ps->index); switch (ps->src.contents[ps->index]) { case L'@': case L'*': case L'#': case L'?': case L'-': case L'$': case L'!': namelen = 1; goto success; } if (!is_portable_name_char(ps->src.contents[ps->index])) goto error; if (iswdigit(ps->src.contents[ps->index])) namelen = 1; else namelen = count_name_length(ps, is_portable_name_char); success:; paramexp_T *pe = xmalloc(sizeof *pe); pe->pe_type = PT_NONE; pe->pe_name = xwcsndup(&ps->src.contents[ps->index], namelen); pe->pe_start = pe->pe_end = pe->pe_match = pe->pe_subst = NULL; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_PARAM; result->wu_param = pe; ps->index += namelen; return result; error: ps->index--; assert(ps->src.contents[ps->index] == L'$'); return NULL; } /* Parses a parameter expansion that starts with "${". * The current position must be at the opening brace L'{' when this function is * called and the position is advanced to the closing brace L'}'. */ wordunit_T *parse_paramexp_in_brace(parsestate_T *ps) { paramexp_T *pe = xmalloc(sizeof *pe); pe->pe_type = 0; pe->pe_name = NULL; pe->pe_start = pe->pe_end = pe->pe_match = pe->pe_subst = NULL; assert(ps->src.contents[ps->index] == L'{'); ps->index++; /* parse PT_NUMBER */ maybe_line_continuations(ps, ps->index); if (ps->src.contents[ps->index] == L'#') { maybe_line_continuations(ps, ps->index + 1); switch (ps->src.contents[ps->index + 1]) { case L'\0': case L'}': case L'+': case L'=': case L':': case L'/': case L'%': break; case L'-': case L'?': case L'#': maybe_line_continuations(ps, ps->index + 2); if (ps->src.contents[ps->index + 2] != L'}') break; /* falls thru! */ default: pe->pe_type |= PT_NUMBER; ps->index++; break; } } /* parse nested expansion */ // maybe_line_continuations(ps, ps->index); // already called above if (!posixly_correct && ps->src.contents[ps->index] == L'{') { pe->pe_type |= PT_NEST; pe->pe_nest = parse_paramexp_in_brace(ps); } else if (!posixly_correct && (ps->src.contents[ps->index] == L'`' || (ps->src.contents[ps->index] == L'$' && (maybe_line_continuations(ps, ps->index + 1), ps->src.contents[ps->index + 1] == L'{' || ps->src.contents[ps->index + 1] == L'(')))) { size_t neststartindex = ps->index; pe->pe_nest = parse_special_word_unit(ps, false); if (ps->index == neststartindex) goto parse_name; pe->pe_type |= PT_NEST; maybe_line_continuations(ps, ps->index); } else { parse_name:; /* no nesting: parse parameter name normally */ size_t namestartindex = ps->index; switch (ps->src.contents[ps->index]) { case L'@': case L'*': case L'#': case L'?': case L'-': case L'$': case L'!': ps->index++; break; default: while (maybe_line_continuations(ps, ps->index), is_name_char(ps->src.contents[ps->index])) ps->index++; break; } size_t namelen = ps->index - namestartindex; if (namelen == 0) { serror(ps, Ngt("the parameter name is missing or invalid")); goto end; } pe->pe_name = xwcsndup(&ps->src.contents[namestartindex], namelen); } /* parse indices */ // maybe_line_continuations(ps, ps->index); // already called above if (!posixly_correct && ps->src.contents[ps->index] == L'[') { ps->index++; pe->pe_start = parse_word(ps, is_comma_or_closing_bracket); if (pe->pe_start == NULL) serror(ps, Ngt("the index is missing")); if (ps->src.contents[ps->index] == L',') { ps->index++; pe->pe_end = parse_word(ps, is_comma_or_closing_bracket); if (pe->pe_end == NULL) serror(ps, Ngt("the index is missing")); } if (ps->src.contents[ps->index] == L']') { maybe_line_continuations(ps, ++ps->index); } else { serror(ps, Ngt("`%ls' is missing"), L"]"); } } /* parse PT_COLON */ // maybe_line_continuations(ps, ps->index); // already called above if (ps->src.contents[ps->index] == L':') { pe->pe_type |= PT_COLON; maybe_line_continuations(ps, ++ps->index); } /* parse '-', '+', '#', etc. */ // maybe_line_continuations(ps, ps->index); // already called above switch (ps->src.contents[ps->index]) { case L'-': pe->pe_type |= PT_MINUS; goto parse_subst; case L'+': pe->pe_type |= PT_PLUS; goto parse_subst; case L'=': pe->pe_type |= PT_ASSIGN; goto parse_subst; case L'?': pe->pe_type |= PT_ERROR; goto parse_subst; case L'#': pe->pe_type |= PT_MATCH | PT_MATCHHEAD; goto parse_match; case L'%': pe->pe_type |= PT_MATCH | PT_MATCHTAIL; goto parse_match; case L'/': if (posixly_correct) serror(ps, Ngt("invalid character `%lc' in parameter expansion"), (wint_t) L'/'); pe->pe_type |= PT_SUBST | PT_MATCHLONGEST; goto parse_match; case L'\0': case L'\n': case L'}': pe->pe_type |= PT_NONE; if (pe->pe_type & PT_COLON) serror(ps, Ngt("invalid use of `%lc' in parameter expansion"), (wint_t) L':'); goto check_closing_brace; default: serror(ps, Ngt("invalid character `%lc' in parameter expansion"), (wint_t) ps->src.contents[ps->index]); goto end; } parse_match: maybe_line_continuations(ps, ps->index + 1); if (pe->pe_type & PT_COLON) { if ((pe->pe_type & PT_MASK) == PT_SUBST) pe->pe_type |= PT_MATCHHEAD | PT_MATCHTAIL; else serror(ps, Ngt("invalid use of `%lc' in parameter expansion"), (wint_t) L':'); maybe_line_continuations(ps, ++ps->index); } else if (ps->src.contents[ps->index] == ps->src.contents[ps->index + 1]) { if ((pe->pe_type & PT_MASK) == PT_MATCH) pe->pe_type |= PT_MATCHLONGEST; else pe->pe_type |= PT_SUBSTALL; ps->index += 2; } else if (ps->src.contents[ps->index] == L'/') { if (ps->src.contents[ps->index + 1] == L'#') { pe->pe_type |= PT_MATCHHEAD; ps->index += 2; } else if (ps->src.contents[ps->index + 1] == L'%') { pe->pe_type |= PT_MATCHTAIL; ps->index += 2; } else { ps->index += 1; } } else { ps->index += 1; } if ((pe->pe_type & PT_MASK) == PT_MATCH) { pe->pe_match = parse_word(ps, is_closing_brace); goto check_closing_brace; } else { pe->pe_match = parse_word(ps, is_slash_or_closing_brace); // maybe_line_continuations(ps, ps->index); // called in parse_word if (ps->src.contents[ps->index] != L'/') goto check_closing_brace; } parse_subst: ps->index++; pe->pe_subst = parse_word(ps, is_closing_brace); check_closing_brace: // maybe_line_continuations(ps, ps->index); // already called above if (ps->src.contents[ps->index] == L'}') ps->index++; else serror(ps, Ngt("`%ls' is missing"), L"}"); if ((pe->pe_type & PT_NUMBER) && (pe->pe_type & PT_MASK) != PT_NONE) serror(ps, Ngt("invalid use of `%lc' in parameter expansion"), (wint_t) L'#'); end:; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_PARAM; result->wu_param = pe; return result; } /* Parses a command substitution that starts with "$(". * When this function is called, `ps->next_index' must be just after the opening * "(". When this function returns, `ps->index' is just after the closing ")". */ wordunit_T *parse_cmdsubst_in_paren(parsestate_T *ps) { embedcmd_T cmd = extract_command_in_paren(ps); maybe_line_continuations(ps, ps->index); if (ps->src.contents[ps->index] == L')') ps->index++; else serror(ps, Ngt("`%ls' is missing"), L")"); wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_CMDSUB; result->wu_cmdsub = cmd; return result; } /* Extracts commands between '(' and ')'. * When this function is called, `ps->next_index' must be just after the opening * "(". When this function returns, the current token will be the closing ")". */ embedcmd_T extract_command_in_paren(parsestate_T *ps) { plist_T save_pending_heredocs; embedcmd_T result; assert(ps->next_index > 0); assert(ps->src.contents[ps->next_index - 1] == L'('); save_pending_heredocs = ps->pending_heredocs; pl_init(&ps->pending_heredocs); if (posixly_correct && ps->info->enable_alias) { result.is_preparsed = false; result.value.unparsed = extract_command_in_paren_unparsed(ps); } else { next_token(ps); result.is_preparsed = true; result.value.preparsed = parse_compound_list(ps); } pl_destroy(&ps->pending_heredocs); ps->pending_heredocs = save_pending_heredocs; return result; } /* Parses commands between '(' and ')'. * The current token must be the opening parenthesis L'(' when this function is * called. The current token is advanced to the closing parenthesis L')'. */ wchar_t *extract_command_in_paren_unparsed(parsestate_T *ps) { bool save_enable_alias = ps->enable_alias; ps->enable_alias = false; size_t startindex = ps->next_index; next_token(ps); andorsfree(parse_compound_list(ps)); assert(startindex <= ps->index); wchar_t *result = xwcsndup( &ps->src.contents[startindex], ps->index - startindex); ps->enable_alias = save_enable_alias; return result; } /* Parses a command substitution enclosed by backquotes. * When this function is called, the current position must be at the character * that just follows the opening backquote L'`'. This function advances the * position to the character that just follows the closing backquote L'`'. * If `bsbq' is true, backslash-escaped backquotes are handled; otherwise, they * are left intact. */ wordunit_T *parse_cmdsubst_in_backquote(parsestate_T *ps, bool bsbq) { assert(ps->src.contents[ps->index - 1] == L'`'); xwcsbuf_T buf; wb_init(&buf); for (;;) { maybe_line_continuations(ps, ps->index); switch (ps->src.contents[ps->index]) { case L'\0': serror(ps, Ngt("the backquoted command substitution is not closed")); goto end; case L'`': ps->index++; goto end; case L'\\': ps->index++; switch (ps->src.contents[ps->index]) { case L'$': case L'`': case L'\\': goto default_; case L'"': if (bsbq) goto default_; /* falls thru! */ default: wb_wccat(&buf, L'\\'); continue; } case L'\n': ps->info->lineno++; /* falls thru! */ default: default_: wb_wccat(&buf, ps->src.contents[ps->index]); ps->index++; break; } } end:; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_CMDSUB; result->wu_cmdsub.is_preparsed = false; result->wu_cmdsub.value.unparsed = wb_towcs(&buf); return result; } /* Parses an arithmetic expansion. * The current position must be at the first opening parenthesis L'(' when this * function is called and the position is advanced to the character that just * follows the last closing parenthesis L')'. If there is no arithmetic * expansion, the return value is NULL and the position is not moved. */ wordunit_T *tryparse_arith(parsestate_T *ps) { size_t saveindex = ps->index; assert(ps->src.contents[ps->index] == L'(' && ps->src.contents[ps->index + 1] == L'('); ps->index += 2; wordunit_T *first = NULL, **lastp = &first; size_t startindex = ps->index; int nestparen = 0; for (;;) { maybe_line_continuations(ps, ps->index); switch (ps->src.contents[ps->index]) { case L'\0': serror(ps, Ngt("`%ls' is missing"), L"))"); goto end; case L'\\': if (ps->src.contents[ps->index + 1] != L'\0') { assert(ps->src.contents[ps->index + 1] != L'\n'); ps->index += 2; continue; } break; case L'\n': ps->info->lineno++; break; case L'$': case L'`': MAKE_WORDUNIT_STRING; wordunit_T *wu = parse_special_word_unit(ps, false); startindex = ps->index; if (wu != NULL) { *lastp = wu; lastp = &wu->next; continue; } else if (ps->src.contents[ps->index] == L'\0') { continue; } break; case L'(': nestparen++; break; case L')': nestparen--; if (nestparen >= 0) break; maybe_line_continuations(ps, ps->index + 1); switch (ps->src.contents[ps->index + 1]) { case L')': MAKE_WORDUNIT_STRING; ps->index += 2; goto end; case L'\0': serror(ps, Ngt("`%ls' is missing"), L")"); goto end; default: goto not_arithmetic_expansion; } default: break; } ps->index++; } end:; wordunit_T *result = xmalloc(sizeof *result); result->next = NULL; result->wu_type = WT_ARITH; result->wu_arith = first; return result; not_arithmetic_expansion: wordfree(first); rewind_index(ps, saveindex); return NULL; } /***** Newline token parser *****/ /* Parses the newline token at the current position and proceeds to the next * line. The contents of pending here-documents are read if any. The current * token is cleared. */ void next_line(parsestate_T *ps) { assert(ps->src.contents[ps->index] == L'\n'); ps->index++; ps->info->lineno++; for (size_t i = 0; i < ps->pending_heredocs.length; i++) read_heredoc_contents(ps, ps->pending_heredocs.contents[i]); pl_clear(&ps->pending_heredocs, 0); wordfree(ps->token); ps->token = NULL; ps->tokentype = TT_UNKNOWN; ps->next_index = ps->index; } /* Processes a sequence of newline tokens. Returns true if at least one newline * token has been processed; false if none. */ bool parse_newline_list(parsestate_T *ps) { bool found = false; while (ps->tokentype == TT_NEWLINE) { found = true; next_line(ps); next_token(ps); } return found; } /***** Character classifiers *****/ /* Checks if the specified character is a token separator. */ bool is_token_delimiter_char(wchar_t c) { switch (c) { case L'\0': case L'\n': case L';': case L'&': case L'|': case L'<': case L'>': case L'(': case L')': return true; default: return iswblank(c); } } bool is_comma_or_closing_bracket(wchar_t c) { return c == L']' || c == L','; } bool is_slash_or_closing_brace(wchar_t c) { return c == L'/' || c == L'}'; } bool is_closing_brace(wchar_t c) { return c == L'}'; } /***** Aliases *****/ /* Performs alias substitution with the given parse state. Proceeds to the * next token if substitution occurred. This function does not substitute an * IO_NUMBER token, but do a keyword token. */ bool psubstitute_alias(parsestate_T *ps, substaliasflags_T flags) { if (!ps->enable_alias) return false; if (ps->tokentype == TT_IO_NUMBER) return false; if (!is_single_string_word(ps->token)) return false; bool substituted = substitute_alias_range( &ps->src, ps->index, ps->next_index, &ps->aliases, flags); if (substituted) { /* parse the result of the substitution. */ ps->next_index = ps->index; next_token(ps); } return substituted; } /* Performs alias substitution recursively. This should not be used where the * substitution result may be recognized as a keyword, since keywords should not * be alias-substituted. */ void psubstitute_alias_recursive(parsestate_T *ps, substaliasflags_T flags) { while (psubstitute_alias(ps, flags)) ; } /***** Syntax parser functions *****/ /* Parses commands. * If `toeol' is true, commands are parsed up to the end of the current input; * otherwise, up to the next closing token. */ and_or_T *parse_command_list(parsestate_T *ps, bool toeol) { and_or_T *first = NULL, **lastp = &first; bool saveerror = ps->error; bool need_separator = false; /* For a command to be parsed after another, it must be separated by L"&", * L";", or newlines. */ if (!toeol && !ps->info->interactive) ps->error = false; if (ps->tokentype == TT_UNKNOWN) next_token(ps); while (!ps->error) { if (toeol) { if (ps->tokentype == TT_NEWLINE) { next_line(ps); need_separator = false; if (ps->next_index != ps->src.length) { next_token(ps); continue; } wordfree(ps->token); ps->token = NULL; ps->index = ps->next_index; ps->tokentype = TT_END_OF_INPUT; } if (ps->tokentype == TT_END_OF_INPUT) { break; } else if (ps->tokentype == TT_RPAREN) { print_errmsg_token_unexpected(ps); break; } else if (need_separator) { serror(ps, Ngt("`;' or `&' is missing")); break; } } else { if (parse_newline_list(ps)) need_separator = false; if (need_separator || ps->tokentype == TT_END_OF_INPUT || is_closing_tokentype(ps->tokentype)) break; } and_or_T *ao = parse_and_or_list(ps); if (ao != NULL) { *lastp = ao; lastp = &ao->next; } if (ps->reparse) { ps->reparse = false; assert(ao == NULL); continue; } if (ps->tokentype != TT_AMP && ps->tokentype != TT_SEMICOLON) { need_separator = true; } else { need_separator = false; next_token(ps); } } if (!toeol) ps->error |= saveerror; return first; } /* Parses commands until a closing token is found. */ and_or_T *parse_compound_list(parsestate_T *ps) { return parse_command_list(ps, false); } /* Parses one and/or list. * The result reflects the trailing "&" or ";", but `ps->index' points to the * delimiter "&" or ";" when the function returns. * If the first word was alias-substituted, the `ps->reparse' flag is set and * NULL is returned. */ and_or_T *parse_and_or_list(parsestate_T *ps) { pipeline_T *p = parse_pipelines_in_and_or(ps); if (ps->reparse) { assert(p == NULL); return NULL; } and_or_T *result = xmalloc(sizeof *result); result->next = NULL; result->ao_pipelines = p; result->ao_async = (ps->tokentype == TT_AMP); return result; } /* Parses all pipelines in one and/or list. * If the first word was alias-substituted, the `ps->reparse' flag is set and * NULL is returned. */ pipeline_T *parse_pipelines_in_and_or(parsestate_T *ps) { pipeline_T *first = NULL, **lastp = &first; bool cond = false; for (;;) { pipeline_T *p = parse_pipeline(ps); if (p != NULL) { p->pl_cond = cond; *lastp = p; lastp = &p->next; } if (ps->reparse) { assert(p == NULL); if (first != NULL) { ps->reparse = false; goto next; } else { break; } } if (ps->tokentype == TT_AMPAMP) cond = true; else if (ps->tokentype == TT_PIPEPIPE) cond = false; else break; next_token(ps); next: parse_newline_list(ps); } return first; } /* Parses one pipeline. * If the first word was alias-substituted, the `ps->reparse' flag is set and * NULL is returned. */ pipeline_T *parse_pipeline(parsestate_T *ps) { bool neg; command_T *c; if (ps->tokentype == TT_BANG) { neg = true; if (posixly_correct && ps->src.contents[ps->next_index] == L'(') serror(ps, Ngt("ksh-like extended glob pattern `!(...)' " "is not supported")); next_token(ps); parse_after_bang: c = parse_commands_in_pipeline(ps); if (ps->reparse) { ps->reparse = false; assert(c == NULL); goto parse_after_bang; } } else { neg = false; c = parse_commands_in_pipeline(ps); if (ps->reparse) { assert(c == NULL); return NULL; } } pipeline_T *result = xmalloc(sizeof *result); result->next = NULL; result->pl_commands = c; result->pl_neg = neg; result->pl_cond = false; return result; } /* Parses the body of the pipeline. * If the first word was alias-substituted, the `ps->reparse' flag is set and * NULL is returned. */ command_T *parse_commands_in_pipeline(parsestate_T *ps) { command_T *first = NULL, **lastp = &first; for (;;) { command_T *c = parse_command(ps); if (c != NULL) { *lastp = c; lastp = &c->next; } if (ps->reparse) { assert(c == NULL); if (first != NULL) { ps->reparse = false; goto next; } else { break; } } if (ps->tokentype != TT_PIPE) break; next_token(ps); next: parse_newline_list(ps); } return first; } /* Parses one command. * If the first word was alias-substituted, the `ps->reparse' flag is set and * NULL is returned. */ command_T *parse_command(parsestate_T *ps) { if (ps->tokentype == TT_BANG || ps->tokentype == TT_IN || is_closing_tokentype(ps->tokentype)) { print_errmsg_token_unexpected(ps); return NULL; } command_T *result = parse_compound_command(ps); if (result != NULL) return result; if (psubstitute_alias(ps, AF_NONGLOBAL)) { /* After alias substitution, we need to re-parse the new current token, * which may be consumed by the caller of this function. We set the * `reparse' flag to tell the caller to re-parse the token. The caller * will reset the flag when staring re-parsing. */ ps->reparse = true; return NULL; } /* parse as a simple command */ result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_lineno = ps->info->lineno; result->c_type = CT_SIMPLE; result->c_assigns = NULL; result->c_redirs = NULL; result->c_words = parse_simple_command_tokens( ps, &result->c_assigns, &result->c_redirs); if (result->c_words[0] == NULL && result->c_assigns == NULL && result->c_redirs == NULL) { /* an empty command */ comsfree(result); if (ps->tokentype == TT_END_OF_INPUT || ps->tokentype == TT_NEWLINE) serror(ps, Ngt("a command is missing at the end of input")); else serror(ps, Ngt("a command is missing before `%lc'"), (wint_t) ps->src.contents[ps->index]); return NULL; } return try_reparse_as_function(ps, result); } /* Parses assignments, redirections, and words. * Assignments are parsed before words are parsed. Tokens are subject to * any-type alias substitution until the first word is parsed, except for the * first token which is parsed intact. The other words are subject to global * alias substitution. Redirections can appear anywhere. * Parsed Assignments and redirections are assigned to `*assigns' and `redirs', * respectively. They must have been initialized NULL (or anything) before * calling this function. Parsed words are returned as a newly-malloced * NULL-terminated array of pointers to newly-malloced wordunit_T's. */ void **parse_simple_command_tokens( parsestate_T *ps, assign_T **assigns, redir_T **redirs) { bool is_first = true; plist_T words; pl_init(&words); next: if (is_first) is_first = false; else psubstitute_alias_recursive(ps, words.length == 0 ? AF_NONGLOBAL : 0); redir_T *redir = tryparse_redirect(ps); if (redir != NULL) { *redirs = redir; redirs = &redir->next; goto next; } if (words.length == 0) { assign_T *assign = tryparse_assignment(ps); if (assign != NULL) { *assigns = assign; assigns = &assign->next; goto next; } } if (ps->token != NULL) { pl_add(&words, ps->token), ps->token = NULL; next_token(ps); goto next; } return pl_toary(&words); } /* Parses words. * The resultant words are returned as a newly-malloced NULL-terminated array of * pointers to word units that are cast to (void *). * All words are subject to global alias substitution. * If `skip_newlines' is true, newline operators are skipped. * Words are parsed until an operator token is found. */ void **parse_words(parsestate_T *ps, bool skip_newlines) { plist_T wordlist; pl_init(&wordlist); for (;;) { psubstitute_alias_recursive(ps, 0); if (skip_newlines && parse_newline_list(ps)) continue; if (ps->token == NULL) break; pl_add(&wordlist, ps->token), ps->token = NULL; next_token(ps); } return pl_toary(&wordlist); } /* Parses as many redirections as possible. * The parsing result is assigned to `*redirlastp' * `*redirlastp' must have been initialized to NULL beforehand. */ void parse_redirect_list(parsestate_T *ps, redir_T **lastp) { for (;;) { psubstitute_alias_recursive(ps, 0); redir_T *redir = tryparse_redirect(ps); if (redir == NULL) break; *lastp = redir; lastp = &redir->next; } } /* Re-parses the current token as an assignment word. If successful, the token * is consumed and the assignment is returned. For an array assignment, all * tokens up to (and including) the closing parenthesis are consumed. If * unsuccessful, the current token is not modified and NULL is returned. */ assign_T *tryparse_assignment(parsestate_T *ps) { if (ps->token == NULL) return NULL; if (ps->token->wu_type != WT_STRING) return NULL; const wchar_t *nameend = skip_name(ps->token->wu_string, is_name_char); size_t namelen = nameend - ps->token->wu_string; if (namelen == 0 || *nameend != L'=') return NULL; assign_T *result = xmalloc(sizeof *result); result->next = NULL; result->a_name = xwcsndup(ps->token->wu_string, namelen); /* remove the name and '=' from the token */ size_t index_after_first_token = ps->next_index; wordunit_T *first_token = ps->token; ps->token = NULL; wmemmove(first_token->wu_string, &nameend[1], wcslen(&nameend[1]) + 1); if (first_token->wu_string[0] == L'\0') { wordunit_T *wu = first_token->next; wordunitfree(first_token); first_token = wu; } next_token(ps); if (posixly_correct || first_token != NULL || ps->index != index_after_first_token || ps->tokentype != TT_LPAREN) { /* scalar assignment */ result->a_type = A_SCALAR; result->a_scalar = first_token; } else { /* array assignment */ next_token(ps); result->a_type = A_ARRAY; result->a_array = parse_words(ps, true); if (ps->tokentype == TT_RPAREN) next_token(ps); else serror(ps, Ngt("`%ls' is missing"), L")"); } return result; } /* If there is a redirection at the current position, parses and returns it. * Otherwise, returns NULL without moving the position. */ redir_T *tryparse_redirect(parsestate_T *ps) { int fd; if (ps->tokentype == TT_IO_NUMBER) { unsigned long lfd; wchar_t *endptr; assert(ps->token != NULL); assert(ps->token->wu_type == WT_STRING); assert(ps->token->next == NULL); errno = 0; lfd = wcstoul(ps->token->wu_string, &endptr, 10); if (errno != 0 || lfd > INT_MAX) fd = -1; /* invalid fd */ else fd = (int) lfd; assert(*endptr == L'\0'); next_token(ps); } else if (ps->src.contents[ps->index] == L'<') { fd = STDIN_FILENO; } else if (ps->src.contents[ps->index] == L'>') { fd = STDOUT_FILENO; } else { return NULL; } redir_T *result = xmalloc(sizeof *result); result->next = NULL; result->rd_fd = fd; switch (ps->tokentype) { case TT_LESS: result->rd_type = RT_INPUT; break; case TT_LESSGREATER: result->rd_type = RT_INOUT; break; case TT_LESSAMP: result->rd_type = RT_DUPIN; break; case TT_GREATER: result->rd_type = RT_OUTPUT; break; case TT_GREATERGREATER: result->rd_type = RT_APPEND; break; case TT_GREATERPIPE: result->rd_type = RT_CLOBBER; break; case TT_GREATERAMP: result->rd_type = RT_DUPOUT; break; case TT_GREATERGREATERPIPE: if (posixly_correct) serror(ps, Ngt("pipe redirection is not supported " "in the POSIXly-correct mode")); result->rd_type = RT_PIPE; break; case TT_LESSLPAREN: result->rd_type = RT_PROCIN; goto parse_command; case TT_GREATERLPAREN: result->rd_type = RT_PROCOUT; goto parse_command; case TT_LESSLESS: result->rd_type = RT_HERE; goto parse_here_document_tag; case TT_LESSLESSDASH: result->rd_type = RT_HERERT; goto parse_here_document_tag; case TT_LESSLESSLESS: if (posixly_correct) serror(ps, Ngt("here-string is not supported " "in the POSIXly-correct mode")); result->rd_type = RT_HERESTR; break; default: assert(false); } /* parse redirection target file token */ next_token(ps); validate_redir_operand(ps); result->rd_filename = ps->token, ps->token = NULL; if (result->rd_filename != NULL) next_token(ps); else serror(ps, Ngt("the redirection target is missing")); return result; parse_here_document_tag: next_token(ps); validate_redir_operand(ps); result->rd_hereend = xwcsndup(&ps->src.contents[ps->index], ps->next_index - ps->index); result->rd_herecontent = NULL; if (ps->token == NULL) { serror(ps, Ngt("the end-of-here-document indicator is missing")); } else { pl_add(&ps->pending_heredocs, result); next_token(ps); } return result; parse_command: if (posixly_correct) serror(ps, Ngt("process redirection is not supported " "in the POSIXly-correct mode")); result->rd_command = extract_command_in_paren(ps); if (ps->tokentype == TT_RPAREN) next_token(ps); else serror(ps, Ngt("unclosed process redirection")); return result; } /* Performs alias substitution on the current token. * Rejects the current token if it is an IO_NUMBER token. */ void validate_redir_operand(parsestate_T *ps) { do { if (posixly_correct && ps->tokentype == TT_IO_NUMBER) { assert(ps->next_index > 0); serror(ps, Ngt("put a space between `%lc' and `%lc' " "for disambiguation"), ps->src.contents[ps->next_index - 1], ps->src.contents[ps->next_index]); } } while (psubstitute_alias(ps, 0)); } /* Parses a compound command. * `command' is the name of the command to parse such as "(" and "if". * Returns NULL iff the current token does not start a compound command. */ command_T *parse_compound_command(parsestate_T *ps) { command_T *result; switch (ps->tokentype) { case TT_LPAREN: case TT_LBRACE: result = parse_group(ps); break; case TT_IF: result = parse_if(ps); break; case TT_FOR: result = parse_for(ps); break; case TT_FUNCTION: result = parse_function(ps); break; case TT_WHILE: case TT_UNTIL: result = parse_while(ps); break; case TT_CASE: result = parse_case(ps); break; #if YASH_ENABLE_DOUBLE_BRACKET case TT_DOUBLE_LBRACKET: result = parse_double_bracket(ps); break; #endif default: return NULL; } assert(result->c_redirs == NULL); parse_redirect_list(ps, &result->c_redirs); if (posixly_correct && result->c_redirs != NULL && ps->token != NULL && ps->tokentype != TT_WORD) { /* A token that follows a redirection cannot be a keyword because * none of the rules in POSIX XCU 2.4 apply. This means * { { echo; } } * and * { { echo; } >/dev/null; } * are OK but * { { echo; } >/dev/null } * is not. */ serror(ps, Ngt("unexpected word after redirection")); serror(ps, Ngt("(maybe you missed `%ls'?)"), L";"); } return result; } /* Parses a command group. * The current token must be the starting "(" or "{". Never returns NULL. */ command_T *parse_group(parsestate_T *ps) { commandtype_T type; tokentype_T endtt; const wchar_t *starts, *ends; switch (ps->tokentype) { case TT_LBRACE: type = CT_GROUP; endtt = TT_RBRACE; starts = L"{", ends = L"}"; break; case TT_LPAREN: type = CT_SUBSHELL; endtt = TT_RPAREN; starts = L"(", ends = L")"; break; default: assert(false); } next_token(ps); unsigned long lineno = ps->info->lineno; and_or_T *cmd = parse_compound_list(ps); if (posixly_correct && cmd == NULL) serror(ps, Ngt("commands are missing between `%ls' and `%ls'"), starts, ends); if (ps->tokentype == endtt) next_token(ps); else print_errmsg_token_missing(ps, ends); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = type; result->c_lineno = lineno; result->c_redirs = NULL; result->c_subcmds = cmd; return result; } /* Parses an if command. * The current token must be the starting "if". Never returns NULL. */ command_T *parse_if(parsestate_T *ps) { assert(ps->tokentype == TT_IF); next_token(ps); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_IF; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_ifcmds = NULL; ifcommand_T **lastp = &result->c_ifcmds; bool after_else = false; while (!ps->error) { ifcommand_T *ic = xmalloc(sizeof *ic); *lastp = ic; lastp = &ic->next; ic->next = NULL; if (!after_else) { ic->ic_condition = parse_compound_list(ps); if (posixly_correct && ic->ic_condition == NULL) serror(ps, Ngt("commands are missing between `%ls' and `%ls'"), (result->c_ifcmds->next == NULL) ? L"if" : L"elif", L"then"); if (ps->tokentype == TT_THEN) next_token(ps); else print_errmsg_token_missing(ps, L"then"); } else { ic->ic_condition = NULL; } ic->ic_commands = parse_compound_list(ps); if (posixly_correct && ic->ic_commands == NULL) serror(ps, Ngt("commands are missing after `%ls'"), after_else ? L"else" : L"then"); if (!after_else && ps->tokentype == TT_ELSE) { next_token(ps); after_else = true; } else if (!after_else && ps->tokentype == TT_ELIF) { next_token(ps); } else if (ps->tokentype == TT_FI) { next_token(ps); break; } else { print_errmsg_token_missing(ps, L"fi"); break; } } return result; } /* Parses a for command. * The current token must be the starting "for". Never returns NULL. */ command_T *parse_for(parsestate_T *ps) { assert(ps->tokentype == TT_FOR); next_token(ps); psubstitute_alias_recursive(ps, 0); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_FOR; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_forname = xwcsndup(&ps->src.contents[ps->index], ps->next_index - ps->index); if (!is_name_word(ps->token)) { if (ps->token == NULL) serror(ps, Ngt("an identifier is required after `for'")); else serror(ps, Ngt("`%ls' is not a valid identifier"), result->c_forname); } next_token(ps); bool on_next_line = false; parse_in: on_next_line |= parse_newline_list(ps); if (ps->tokentype == TT_IN) { next_token(ps); result->c_forwords = parse_words(ps, false); if (ps->tokentype == TT_SEMICOLON) next_token(ps); } else if (ps->tokentype != TT_DO && psubstitute_alias(ps, 0)) { goto parse_in; } else { result->c_forwords = NULL; if (ps->tokentype == TT_SEMICOLON) { next_token(ps); if (on_next_line) serror(ps, Ngt("`;' cannot appear on a new line")); } } parse_do: parse_newline_list(ps); if (ps->tokentype == TT_DO) next_token(ps); else if (psubstitute_alias(ps, 0)) goto parse_do; else serror(ps, Ngt("`%ls' is missing"), L"do"); // print_errmsg_token_missing(ps, L"do"); result->c_forcmds = parse_compound_list(ps); if (posixly_correct && result->c_forcmds == NULL) serror(ps, Ngt("commands are missing between `%ls' and `%ls'"), L"do", L"done"); if (ps->tokentype == TT_DONE) next_token(ps); else print_errmsg_token_missing(ps, L"done"); return result; } /* Parses a while/until command. * The current token must be the starting "while" or "until". Never returns * NULL. */ command_T *parse_while(parsestate_T *ps) { bool whltype; switch (ps->tokentype) { case TT_WHILE: whltype = true; break; case TT_UNTIL: whltype = false; break; default: assert(false); } next_token(ps); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_WHILE; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_whltype = whltype; result->c_whlcond = parse_compound_list(ps); if (posixly_correct && result->c_whlcond == NULL) serror(ps, Ngt("commands are missing after `%ls'"), whltype ? L"while" : L"until"); if (ps->tokentype == TT_DO) next_token(ps); else print_errmsg_token_missing(ps, L"do"); result->c_whlcmds = parse_compound_list(ps); if (posixly_correct && result->c_whlcmds == NULL) serror(ps, Ngt("commands are missing between `%ls' and `%ls'"), L"do", L"done"); if (ps->tokentype == TT_DONE) next_token(ps); else print_errmsg_token_missing(ps, L"done"); return result; } /* Parses a case command. * The current token must be the starting "case". Never returns NULL. */ command_T *parse_case(parsestate_T *ps) { assert(ps->tokentype == TT_CASE); next_token(ps); psubstitute_alias_recursive(ps, 0); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_CASE; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_casword = ps->token, ps->token = NULL; if (result->c_casword != NULL) next_token(ps); else serror(ps, Ngt("a word is required after `%ls'"), L"case"); parse_in: parse_newline_list(ps); if (ps->tokentype == TT_IN) { next_token(ps); result->c_casitems = parse_case_list(ps); } else if (psubstitute_alias(ps, 0)) { goto parse_in; } else { serror(ps, Ngt("`%ls' is missing"), L"in"); // print_errmsg_token_missing(ps, L"in"); result->c_casitems = NULL; } if (ps->tokentype == TT_ESAC) next_token(ps); else print_errmsg_token_missing(ps, L"esac"); return result; } /* Parses the body of a case command (the part between "in" and "esac"). */ caseitem_T *parse_case_list(parsestate_T *ps) { caseitem_T *first = NULL, **lastp = &first; do { parse_newline_list(ps); if (ps->tokentype == TT_ESAC) break; if (psubstitute_alias(ps, 0)) continue; caseitem_T *ci = xmalloc(sizeof *ci); *lastp = ci; lastp = &ci->next; ci->next = NULL; ci->ci_patterns = parse_case_patterns(ps); ci->ci_commands = parse_compound_list(ps); /* `ci_commands' may be NULL unlike for and while commands */ if (ps->tokentype == TT_DOUBLE_SEMICOLON) next_token(ps); else break; } while (!ps->error); return first; } /* Parses patterns of a case item. * This function consumes the closing ")". * Perform alias substitution before calling this function. */ void **parse_case_patterns(parsestate_T *ps) { plist_T wordlist; pl_init(&wordlist); if (ps->tokentype == TT_LPAREN) { /* ignore the first '(' */ next_token(ps); do { if (posixly_correct && ps->tokentype == TT_ESAC) serror(ps, Ngt("an unquoted `esac' cannot be the first case pattern")); } while (psubstitute_alias(ps, 0)); } const wchar_t *predecessor = L"("; do { if (ps->token == NULL) { if (ps->tokentype == TT_END_OF_INPUT) { // serror(ps, ...); } else if (ps->tokentype == TT_NEWLINE) { serror(ps, Ngt("a word is required after `%ls'"), predecessor); } else { serror(ps, Ngt("encountered an invalid character `%lc' " "in the case pattern"), (wint_t) ps->src.contents[ps->index]); } break; } pl_add(&wordlist, ps->token), ps->token = NULL; next_token(ps); psubstitute_alias_recursive(ps, 0); if (ps->tokentype != TT_PIPE) { if (ps->tokentype == TT_RPAREN) next_token(ps); else serror(ps, Ngt("`%ls' is missing"), L")"); break; } predecessor = L"|"; next_token(ps); psubstitute_alias_recursive(ps, 0); } while (!ps->error); return pl_toary(&wordlist); } #if YASH_ENABLE_DOUBLE_BRACKET /* Parses a double-bracket command. * The current token must be the starting "[[". * Never returns NULL. The resultant `c_dbexp' may contain NULL sub-expression * in case of syntax error. */ command_T *parse_double_bracket(parsestate_T *ps) { if (posixly_correct) serror(ps, Ngt("The [[ ... ]] syntax is not supported " "in the POSIXly-correct mode")); assert(ps->tokentype == TT_DOUBLE_LBRACKET); next_token(ps); psubstitute_alias_recursive(ps, 0); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_BRACKET; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_dbexp = parse_double_bracket_ors(ps); if (is_single_string_word(ps->token) && wcscmp(ps->token->wu_string, L"]]") == 0) { next_token(ps); psubstitute_alias_recursive(ps, 0); } else if (!ps->error) { if (ps->tokentype == TT_NEWLINE) serror(ps, Ngt("`%ls' is missing"), L"]]"); else print_errmsg_token(ps, Ngt("invalid word `%ls' between `[[' and `]]'")); } return result; } /* Parses one or more "and" expressions separated by "||"s in the double-bracket * command. May return NULL on error. */ dbexp_T *parse_double_bracket_ors(parsestate_T *ps) { dbexp_T *lhs = parse_double_bracket_ands(ps); if (lhs == NULL || ps->tokentype != TT_PIPEPIPE) return lhs; next_token(ps); psubstitute_alias_recursive(ps, 0); dbexp_T *result = xmalloc(sizeof *result); result->type = DBE_OR; result->operator = NULL; result->lhs.subexp = lhs; result->rhs.subexp = parse_double_bracket_ors(ps); return result; } /* Parses one or more "!" expressions separated by "&&"s in the double-bracket * command. May return NULL on error. */ dbexp_T *parse_double_bracket_ands(parsestate_T *ps) { dbexp_T *lhs = parse_double_bracket_nots(ps); if (lhs == NULL || ps->tokentype != TT_AMPAMP) return lhs; next_token(ps); psubstitute_alias_recursive(ps, 0); dbexp_T *result = xmalloc(sizeof *result); result->type = DBE_AND; result->operator = NULL; result->lhs.subexp = lhs; result->rhs.subexp = parse_double_bracket_ands(ps); return result; } /* Parses a primary expression optionally prefixed by any number of "!"s in the * double-bracket command. May return NULL on error. */ dbexp_T *parse_double_bracket_nots(parsestate_T *ps) { if (ps->tokentype != TT_BANG) return parse_double_bracket_primary(ps); next_token(ps); psubstitute_alias_recursive(ps, 0); dbexp_T *result = xmalloc(sizeof *result); result->type = DBE_NOT; result->operator = NULL; result->lhs.subexp = NULL; result->rhs.subexp = parse_double_bracket_nots(ps); return result; } /* Parses a primary expression in the double-bracket command. May return NULL on * error. The "(...)" operator is considered as a primary in this function. */ dbexp_T *parse_double_bracket_primary(parsestate_T *ps) { if (ps->tokentype == TT_LPAREN) { /* parse "(...)" */ next_token(ps); psubstitute_alias_recursive(ps, 0); dbexp_T *subexp = parse_double_bracket_ors(ps); if (ps->tokentype == TT_RPAREN) { next_token(ps); psubstitute_alias_recursive(ps, 0); } else if (!ps->error) { if (ps->tokentype == TT_NEWLINE || (is_single_string_word(ps->token) && wcscmp(ps->token->wu_string, L"]]") == 0)) serror(ps, Ngt("`%ls' is missing"), L")"); else print_errmsg_token(ps, Ngt("invalid word `%ls' between `[[' and `]]'")); } return subexp; } dbexptype_T type; wchar_t *op; wordunit_T *lhs, *rhs; if (is_single_string_word(ps->token) && is_unary_primary(ps->token->wu_string)) { type = DBE_UNARY; lhs = NULL; goto parse_primary_operator; } lhs = parse_double_bracket_operand(ps); if (lhs == NULL) return NULL; if (ps->tokentype == TT_LESS || ps->tokentype == TT_GREATER) { type = DBE_BINARY; op = xwcsndup(&ps->src.contents[ps->index], ps->next_index - ps->index); } else if (is_single_string_word(ps->token) && is_binary_primary(ps->token->wu_string)) { type = DBE_BINARY; parse_primary_operator: op = ps->token->wu_string, ps->token->wu_string = NULL; } else { type = DBE_STRING; op = NULL; rhs = lhs, lhs = NULL; goto return_result; } next_token(ps); psubstitute_alias_recursive(ps, 0); rhs = parse_double_bracket_operand(ps); return_result:; dbexp_T *result = xmalloc(sizeof *result); result->type = type; result->operator = op; result->lhs.word = lhs; result->rhs.word = rhs; return result; } /* Parses a operand token of a primary conditional expression in the double- * bracket command. Returns NULL on error. */ wordunit_T *parse_double_bracket_operand(parsestate_T *ps) { if (is_single_string_word(ps->token) && wcscmp(ps->token->wu_string, L"]]") == 0) { serror(ps, Ngt("conditional expression " "is missing or incomplete between `[[' and `]]'")); return NULL; } if (ps->token == NULL) { if (ps->tokentype == TT_NEWLINE) serror(ps, Ngt("unexpected linebreak " "in the middle of the [[ ... ]] command")); else print_errmsg_token(ps, Ngt("`%ls' is not a valid operand " "in the conditional expression")); return NULL; } wordunit_T *result = ps->token; ps->token = NULL; next_token(ps); psubstitute_alias_recursive(ps, 0); return result; } #endif /* YASH_ENABLE_DOUBLE_BRACKET */ /* Parses a function definition that starts with the "function" keyword. * The current token must be "function". Never returns NULL. */ command_T *parse_function(parsestate_T *ps) { if (posixly_correct) serror(ps, Ngt("`%ls' cannot be used as a command name"), L"function"); assert(ps->tokentype == TT_FUNCTION); next_token(ps); psubstitute_alias_recursive(ps, 0); command_T *result = xmalloc(sizeof *result); result->next = NULL; result->refcount = 1; result->c_type = CT_FUNCDEF; result->c_lineno = ps->info->lineno; result->c_redirs = NULL; result->c_funcname = ps->token, ps->token = NULL; if (result->c_funcname == NULL) serror(ps, Ngt("a word is required after `%ls'"), L"function"); bool paren = false; next_token(ps); parse_parentheses: if (ps->tokentype == TT_LPAREN) { size_t saveindex = ps->index; next_token(ps); parse_close_parenthesis: if (ps->tokentype == TT_RPAREN) { paren = true; next_token(ps); } else if (psubstitute_alias(ps, AF_NONGLOBAL)) { goto parse_close_parenthesis; } else { /* rewind to '(' */ rewind_index(ps, saveindex); ps->next_index = ps->index; next_token(ps); } } parse_function_body: parse_newline_list(ps); result->c_funcbody = parse_compound_command(ps); if (result->c_funcbody == NULL) { if (psubstitute_alias(ps, 0)) { if (paren) goto parse_function_body; else goto parse_parentheses; } serror(ps, Ngt("a function body must be a compound command")); } return result; } /* Parses (part of) a function definition command that does not start with the * "function" keyword. This function must be called just after a simple command * has been parsed, which is given as `c'. If the next character is '(', it * should signify a function definition, so this function continues parsing the * rest of it. Otherwise, `c' is returned intact. * If successful, `c' is directly modified to the function definition parsed. */ command_T *try_reparse_as_function(parsestate_T *ps, command_T *c) { if (ps->tokentype != TT_LPAREN) // not a function definition? return c; /* If this is a function definition, there must be exactly one command word * before '('. */ assert(c->c_type == CT_SIMPLE); if (c->c_redirs != NULL || c->c_assigns != NULL || c->c_words[0] == NULL || c->c_words[1] != NULL) { serror(ps, Ngt("invalid use of `%lc'"), (wint_t) L'('); return c; } /* The name must be valid. */ wordunit_T *name = c->c_words[0]; if (!is_name_word(name)) { serror(ps, Ngt("invalid function name")); return c; } /* Skip '('. */ next_token(ps); /* Parse ')'. */ psubstitute_alias_recursive(ps, 0); if (ps->tokentype != TT_RPAREN) { serror(ps, Ngt("`(' must be followed by `)' in a function definition")); return c; } next_token(ps); free(c->c_words); c->c_type = CT_FUNCDEF; c->c_funcname = name; parse_function_body: parse_newline_list(ps); c->c_funcbody = parse_compound_command(ps); if (c->c_funcbody == NULL) { if (psubstitute_alias(ps, 0)) goto parse_function_body; serror(ps, Ngt("a function body must be a compound command")); } return c; } /***** Here-document contents *****/ /* Reads the contents of a here-document. */ void read_heredoc_contents(parsestate_T *ps, redir_T *r) { if (wcschr(r->rd_hereend, L'\n') != NULL) { serror(ps, Ngt("the end-of-here-document indicator contains a newline")); return; } assert(r->rd_type == RT_HERE || r->rd_type == RT_HERERT); if (wcspbrk(r->rd_hereend, QUOTES) != NULL) read_heredoc_contents_without_expansion(ps, r); else read_heredoc_contents_with_expansion(ps, r); } /* Reads the contents of a here-document without any parameter expansions. */ void read_heredoc_contents_without_expansion(parsestate_T *ps, redir_T *r) { wchar_t *eoc = unquote(r->rd_hereend); // end-of-contents marker bool skiptab = (r->rd_type == RT_HERERT); xwcsbuf_T buf; wb_init(&buf); while (!is_end_of_heredoc_contents(ps, eoc, skiptab)) { const wchar_t *eol = wcschr(&ps->src.contents[ps->index], L'\n'); size_t linelen; if (eol != NULL) { linelen = eol - &ps->src.contents[ps->index] + 1; } else { /* encountered EOF before reading an end-of-contents marker! */ linelen = ps->src.length - ps->index; serror(ps, Ngt("the here-document content is not closed by `%ls'"), eoc); } wb_ncat_force(&buf, &ps->src.contents[ps->index], linelen); ps->index += linelen; if (eol != NULL) ps->info->lineno++; else break; } free(eoc); wordunit_T *wu = xmalloc(sizeof *wu); wu->next = NULL; wu->wu_type = WT_STRING; wu->wu_string = escape(buf.contents, L"\\"); r->rd_herecontent = wu; wb_destroy(&buf); } /* Reads the contents of a here-document that may contain parameter expansions, * command substitutions and arithmetic expansions. */ void read_heredoc_contents_with_expansion(parsestate_T *ps, redir_T *r) { wordunit_T **lastp = &r->rd_herecontent; const wchar_t *eoc = r->rd_hereend; bool skiptab = (r->rd_type == RT_HERERT); while (!is_end_of_heredoc_contents(ps, eoc, skiptab)) { size_t oldindex = ps->index; lastp = parse_string_without_quotes(ps, true, true, lastp); if (ps->index == oldindex || ps->src.contents[ps->index - 1] != L'\n') { /* encountered EOF before reading an end-of-contents marker! */ serror(ps, Ngt("the here-document content is not closed by `%ls'"), eoc); break; } } } /* Checks if the whole current line is end-of-heredoc `eoc'. * Reads the current line if not yet read. * If `skiptab' is true, leading tabs in the line are skipped. * If an end-of-heredoc is found, returns true and advances the current position * to the next line. Otherwise, returns false with the position unchanged * (except that leading tabs are skipped). */ bool is_end_of_heredoc_contents( parsestate_T *ps, const wchar_t *eoc, bool skiptab) { assert(ps->src.length > 0 && ps->src.contents[ps->index - 1] == L'\n'); if (ps->src.contents[ps->index] == L'\0') if (read_more_input(ps) != INPUT_OK) return false; if (skiptab) while (ps->src.contents[ps->index] == L'\t') ps->index++; const wchar_t *m = matchwcsprefix(&ps->src.contents[ps->index], eoc); if (m != NULL) { size_t matchendindex = m - ps->src.contents; switch (ps->src.contents[matchendindex]) { case L'\0': ps->index = matchendindex; return true; case L'\n': ps->index = matchendindex + 1; ps->info->lineno++; return true; } } return false; } /* Prints an error message for each pending here-document. */ void reject_pending_heredocs(parsestate_T *ps) { for (size_t i = 0; i < ps->pending_heredocs.length; i++) { const redir_T *r = ps->pending_heredocs.contents[i]; const char *operator; switch (r->rd_type) { case RT_HERE: operator = "<<"; break; case RT_HERERT: operator = "<<-"; break; default: assert(false); } serror(ps, Ngt("here-document content for %s%ls is missing"), operator, r->rd_hereend); } } /* Parses a string. * Parameter expansions, command substitutions and arithmetic expansions are * recognized, but single and double quotes are not treated as quotes. * Command substitutions enclosed by backquotes are recognized iff `backquote' * is true. If `stoponnewline' is true, stops parsing right after the next * newline is parsed. Otherwise, parsing continues up to the end of file. * The results are assigned ot `*lastp'. * The return value is a pointer to the `next' member of the last resultant word * unit (or `lastp' if no word unit resulted). */ wordunit_T **parse_string_without_quotes( parsestate_T *ps, bool backquote, bool stoponnewline, wordunit_T **lastp) { size_t startindex = ps->index; for (;;) { maybe_line_continuations(ps, ps->index); switch (ps->src.contents[ps->index]) { case L'\0': goto done; case L'\\': if (ps->src.contents[ps->index + 1] != L'\0') { assert(ps->src.contents[ps->index + 1] != L'\n'); ps->index += 2; continue; } break; case L'\n': ps->info->lineno++; if (stoponnewline) { ps->index++; goto done; } break; case L'`': if (!backquote) break; /* falls thru! */ case L'$': MAKE_WORDUNIT_STRING; wordunit_T *wu = parse_special_word_unit(ps, true); startindex = ps->index; if (wu != NULL) { *lastp = wu; lastp = &wu->next; continue; } else if (ps->src.contents[ps->index] == L'\0') { continue; } break; default: break; } ps->index++; } done: MAKE_WORDUNIT_STRING; return lastp; } /********** Functions that Convert Parse Trees into Strings **********/ struct print { xwcsbuf_T buffer; plist_T pending_heredocs; bool multiline; }; static void print_and_or_lists( struct print *restrict pr, const and_or_T *restrict andors, unsigned indent, bool omitsemicolon) __attribute__((nonnull(1))); static void print_pipelines( struct print *restrict er, const pipeline_T *restrict pipelines, unsigned indent) __attribute__((nonnull(1))); static void print_commands( struct print *restrict pr, const command_T *restrict commands, unsigned indent) __attribute__((nonnull(1))); static void print_one_command( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_simple_command( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_group( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_subshell( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_if( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_for( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_while( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_case( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_caseitems( struct print *restrict pr, const caseitem_T *restrict caseitems, unsigned indent) __attribute__((nonnull(1))); #if YASH_ENABLE_DOUBLE_BRACKET static void print_double_bracket( struct print *restrict pr, const command_T *restrict c, unsigned indent) __attribute__((nonnull)); static void print_double_bracket_expression( struct print *restrict pr, const dbexp_T *restrict e, dbexptype_T context, unsigned indent) __attribute__((nonnull)); #endif static void print_function_definition( struct print *restrict pr, const command_T *restrict command, unsigned indent) __attribute__((nonnull)); static void print_assignments( struct print *restrict pr, const assign_T *restrict assigns, unsigned indent) __attribute__((nonnull(1))); static void print_redirections( struct print *restrict pr, const redir_T *restrict redirections, unsigned indent) __attribute__((nonnull(1))); static void print_word( struct print *restrict pr, const wordunit_T *restrict wordunits, unsigned indent) __attribute__((nonnull(1))); static void print_parameter( struct print *restrict pr, const paramexp_T *restrict parameter, unsigned indent) __attribute__((nonnull)); static void print_embedded_command(struct print *pr, embedcmd_T command, unsigned indent) __attribute__((nonnull)); static void print_indent(struct print *pr, unsigned indent) __attribute__((nonnull)); static void print_space_or_newline(struct print* pr) __attribute__((nonnull)); static void print_pending_heredocs(struct print* pr) __attribute__((nonnull)); static void trim_end_of_buffer(xwcsbuf_T *buf) __attribute__((nonnull)); /* Converts the specified pipelines into a newly malloced wide string. */ wchar_t *pipelines_to_wcs(const pipeline_T *pipelines) { struct print pr; wb_init(&pr.buffer); pl_init(&pr.pending_heredocs); pr.multiline = false; print_pipelines(&pr, pipelines, 0); trim_end_of_buffer(&pr.buffer); pl_destroy(&pr.pending_heredocs); return wb_towcs(&pr.buffer); } /* Converts the specified command into a newly malloced wide string. * If `multiline' is true, the result is pretty-formated with a terminating * newline. */ wchar_t *command_to_wcs(const command_T *command, bool multiline) { struct print pr; wb_init(&pr.buffer); pl_init(&pr.pending_heredocs); pr.multiline = multiline; print_one_command(&pr, command, 0); trim_end_of_buffer(&pr.buffer); if (pr.multiline) print_space_or_newline(&pr); pl_destroy(&pr.pending_heredocs); return wb_towcs(&pr.buffer); } void print_and_or_lists(struct print *restrict pr, const and_or_T *restrict ao, unsigned indent, bool omitsemicolon) { while (ao != NULL) { print_pipelines(pr, ao->ao_pipelines, indent); trim_end_of_buffer(&pr->buffer); if (ao->ao_async) wb_wccat(&pr->buffer, L'&'); else if (!pr->multiline && (ao->next != NULL || !omitsemicolon)) wb_wccat(&pr->buffer, L';'); if (ao->next != NULL || !omitsemicolon) print_space_or_newline(pr); ao = ao->next; } } void print_pipelines(struct print *restrict pr, const pipeline_T *restrict pl, unsigned indent) { if (pl == NULL) return; for (;;) { print_indent(pr, indent); if (pl->pl_neg) wb_cat(&pr->buffer, L"! "); print_commands(pr, pl->pl_commands, indent); pl = pl->next; if (pl == NULL) break; wb_cat(&pr->buffer, pl->pl_cond ? L"&&" : L"||"); print_space_or_newline(pr); } } void print_commands( struct print *restrict pr, const command_T *restrict c, unsigned indent) { if (c == NULL) return; for (;;) { print_one_command(pr, c, indent); c = c->next; if (c == NULL) break; wb_cat(&pr->buffer, L"| "); } } void print_one_command( struct print *restrict pr, const command_T *restrict c, unsigned indent) { switch (c->c_type) { case CT_SIMPLE: print_simple_command(pr, c, indent); break; case CT_GROUP: print_group(pr, c, indent); break; case CT_SUBSHELL: print_subshell(pr, c, indent); break; case CT_IF: print_if(pr, c, indent); break; case CT_FOR: print_for(pr, c, indent); break; case CT_WHILE: print_while(pr, c, indent); break; case CT_CASE: print_case(pr, c, indent); break; #if YASH_ENABLE_DOUBLE_BRACKET case CT_BRACKET: print_double_bracket(pr, c, indent); break; #endif /* YASH_ENABLE_DOUBLE_BRACKET */ case CT_FUNCDEF: print_function_definition(pr, c, indent); assert(c->c_redirs == NULL); return; // break; } print_redirections(pr, c->c_redirs, indent); } void print_simple_command( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_SIMPLE); print_assignments(pr, c->c_assigns, indent); for (size_t i = 0; c->c_words[i] != NULL; i++) { const wordunit_T *wu = c->c_words[i]; /* A simple command can start with a keyword if preceded by a * redirection. Because we print the redirection after the command * words, the keyword must be quoted to be re-parsed as a simple * command. */ if (i == 0 && c->c_assigns == NULL && is_single_string_word(wu) && is_keyword(wu->wu_string)) wb_wccat(&pr->buffer, L'\\'); print_word(pr, wu, indent); wb_wccat(&pr->buffer, L' '); } } void print_group( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_GROUP); wb_wccat(&pr->buffer, L'{'); print_space_or_newline(pr); print_and_or_lists(pr, c->c_subcmds, indent + 1, false); print_indent(pr, indent); wb_cat(&pr->buffer, L"} "); } void print_subshell( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_SUBSHELL); wb_wccat(&pr->buffer, L'('); print_and_or_lists(pr, c->c_subcmds, indent + 1, true); trim_end_of_buffer(&pr->buffer); wb_cat(&pr->buffer, L") "); } void print_if( struct print *restrict pr, const command_T *restrict c, unsigned indent) { const ifcommand_T *ic; assert(c->c_type == CT_IF); wb_cat(&pr->buffer, L"if "); ic = c->c_ifcmds; for (;;) { print_and_or_lists(pr, ic->ic_condition, indent + 1, false); print_indent(pr, indent); wb_cat(&pr->buffer, L"then"); print_space_or_newline(pr); print_and_or_lists(pr, ic->ic_commands, indent + 1, false); ic = ic->next; if (ic == NULL) { break; } else if (!ic->ic_condition && ic->next == NULL) { print_indent(pr, indent); wb_cat(&pr->buffer, L"else"); print_space_or_newline(pr); print_and_or_lists(pr, ic->ic_commands, indent + 1, false); break; } else { print_indent(pr, indent); wb_cat(&pr->buffer, L"elif "); } } print_indent(pr, indent); wb_cat(&pr->buffer, L"fi "); } void print_for( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_FOR); wb_cat(&pr->buffer, L"for "); wb_cat(&pr->buffer, c->c_forname); if (c->c_forwords != NULL) { wb_cat(&pr->buffer, L" in"); for (void **w = c->c_forwords; *w != NULL; w++) { wb_wccat(&pr->buffer, L' '); print_word(pr, *w, indent); } if (!pr->multiline) wb_wccat(&pr->buffer, L';'); print_space_or_newline(pr); } else { wb_wccat(&pr->buffer, L' '); } print_indent(pr, indent); wb_cat(&pr->buffer, L"do"); print_space_or_newline(pr); print_and_or_lists(pr, c->c_forcmds, indent + 1, false); print_indent(pr, indent); wb_cat(&pr->buffer, L"done "); } void print_while( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_WHILE); wb_cat(&pr->buffer, c->c_whltype ? L"while " : L"until "); print_and_or_lists(pr, c->c_whlcond, indent + 1, false); print_indent(pr, indent); wb_cat(&pr->buffer, L"do"); print_space_or_newline(pr); print_and_or_lists(pr, c->c_whlcmds, indent + 1, false); print_indent(pr, indent); wb_cat(&pr->buffer, L"done "); } void print_case( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_CASE); wb_cat(&pr->buffer, L"case "); print_word(pr, c->c_casword, indent); wb_cat(&pr->buffer, L" in"); print_space_or_newline(pr); print_caseitems(pr, c->c_casitems, indent + 1); print_indent(pr, indent); wb_cat(&pr->buffer, L"esac "); } void print_caseitems(struct print *restrict pr, const caseitem_T *restrict ci, unsigned indent) { while (ci != NULL) { print_indent(pr, indent); wb_wccat(&pr->buffer, L'('); for (void **w = ci->ci_patterns; ; ) { print_word(pr, *w, indent); w++; if (*w == NULL) break; wb_cat(&pr->buffer, L" | "); } wb_wccat(&pr->buffer, L')'); print_space_or_newline(pr); if (ci->ci_commands != NULL) { print_and_or_lists(pr, ci->ci_commands, indent + 1, true); print_space_or_newline(pr); } print_indent(pr, indent + 1); wb_cat(&pr->buffer, L";;"); print_space_or_newline(pr); ci = ci->next; } } #if YASH_ENABLE_DOUBLE_BRACKET void print_double_bracket( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_BRACKET); wb_cat(&pr->buffer, L"[[ "); print_double_bracket_expression(pr, c->c_dbexp, DBE_OR, indent); wb_cat(&pr->buffer, L"]] "); } void print_double_bracket_expression( struct print *restrict pr, const dbexp_T *restrict e, dbexptype_T context, unsigned indent) { assert(context == DBE_OR || context == DBE_AND || context == DBE_NOT); switch (e->type) { case DBE_OR: if (context != DBE_OR) wb_cat(&pr->buffer, L"( "); print_double_bracket_expression(pr, e->lhs.subexp, DBE_OR, indent); wb_cat(&pr->buffer, L"|| "); print_double_bracket_expression(pr, e->rhs.subexp, DBE_OR, indent); if (context != DBE_OR) wb_cat(&pr->buffer, L") "); break; case DBE_AND: if (context == DBE_NOT) wb_cat(&pr->buffer, L"( "); print_double_bracket_expression(pr, e->lhs.subexp, DBE_AND, indent); wb_cat(&pr->buffer, L"&& "); print_double_bracket_expression(pr, e->rhs.subexp, DBE_AND, indent); if (context == DBE_NOT) wb_cat(&pr->buffer, L") "); break; case DBE_NOT: wb_cat(&pr->buffer, L"! "); print_double_bracket_expression(pr, e->rhs.subexp, DBE_NOT, indent); break; case DBE_BINARY: print_word(pr, e->lhs.word, indent); wb_wccat(&pr->buffer, L' '); /* falls thru! */ case DBE_UNARY: wb_cat(&pr->buffer, e->operator); wb_wccat(&pr->buffer, L' '); /* falls thru! */ case DBE_STRING: print_word(pr, e->rhs.word, indent); wb_wccat(&pr->buffer, L' '); break; } } #endif void print_function_definition( struct print *restrict pr, const command_T *restrict c, unsigned indent) { assert(c->c_type == CT_FUNCDEF); if (!is_name_word(c->c_funcname)) wb_cat(&pr->buffer, L"function "); print_word(pr, c->c_funcname, indent); wb_cat(&pr->buffer, L"()"); print_space_or_newline(pr); print_indent(pr, indent); assert(c->c_funcbody->next == NULL); print_one_command(pr, c->c_funcbody, indent); } void print_assignments( struct print *restrict pr, const assign_T *restrict a, unsigned indent) { while (a != NULL) { wb_cat(&pr->buffer, a->a_name); wb_wccat(&pr->buffer, L'='); switch (a->a_type) { case A_SCALAR: print_word(pr, a->a_scalar, indent); break; case A_ARRAY: wb_wccat(&pr->buffer, L'('); for (void **w = a->a_array; *w != NULL; w++) { print_word(pr, *w, indent); wb_wccat(&pr->buffer, L' '); } trim_end_of_buffer(&pr->buffer); wb_wccat(&pr->buffer, L')'); break; } wb_wccat(&pr->buffer, L' '); a = a->next; } } void print_redirections( struct print *restrict pr, const redir_T *restrict rd, unsigned indent) { while (rd != NULL) { const wchar_t *s; enum { file, here, proc, } type; switch (rd->rd_type) { case RT_INPUT: s = L"<"; type = file; break; case RT_OUTPUT: s = L">"; type = file; break; case RT_CLOBBER: s = L">|"; type = file; break; case RT_APPEND: s = L">>"; type = file; break; case RT_INOUT: s = L"<>"; type = file; break; case RT_DUPIN: s = L"<&"; type = file; break; case RT_DUPOUT: s = L">&"; type = file; break; case RT_PIPE: s = L">>|"; type = file; break; case RT_HERE: s = L"<<"; type = here; break; case RT_HERERT: s = L"<<-"; type = here; break; case RT_HERESTR: s = L"<<<"; type = file; break; case RT_PROCIN: s = L"<("; type = proc; break; case RT_PROCOUT: s = L">("; type = proc; break; default: assert(false); } wb_wprintf(&pr->buffer, L"%d%ls", rd->rd_fd, s); switch (type) { case file: print_word(pr, rd->rd_filename, indent); break; case here: wb_cat(&pr->buffer, rd->rd_hereend); pl_add(&pr->pending_heredocs, (void *) rd); break; case proc: print_embedded_command(pr, rd->rd_command, indent + 1); wb_wccat(&pr->buffer, L')'); break; } wb_wccat(&pr->buffer, L' '); rd = rd->next; } } void print_word(struct print *restrict pr, const wordunit_T *restrict wu, unsigned indent) { while (wu != NULL) { switch (wu->wu_type) { case WT_STRING: wb_cat(&pr->buffer, wu->wu_string); break; case WT_PARAM: print_parameter(pr, wu->wu_param, indent); break; case WT_CMDSUB: wb_cat(&pr->buffer, L"$("); size_t startindex = pr->buffer.length; print_embedded_command(pr, wu->wu_cmdsub, indent + 1); wb_wccat(&pr->buffer, L')'); if (pr->buffer.contents[startindex] == L'(') wb_insert(&pr->buffer, startindex, L" "); break; case WT_ARITH: wb_cat(&pr->buffer, L"$(("); print_word(pr, wu->wu_arith, indent); wb_cat(&pr->buffer, L"))"); break; } wu = wu->next; } } void print_parameter(struct print *restrict pr, const paramexp_T *restrict pe, unsigned indent) { wb_cat(&pr->buffer, L"${"); if (pe->pe_type & PT_NUMBER) wb_wccat(&pr->buffer, L'#'); if (pe->pe_type & PT_NEST) print_word(pr, pe->pe_nest, indent); else wb_cat(&pr->buffer, pe->pe_name); if (pe->pe_start != NULL) { wb_wccat(&pr->buffer, L'['); print_word(pr, pe->pe_start, indent); if (pe->pe_end != NULL) { wb_wccat(&pr->buffer, L','); print_word(pr, pe->pe_end, indent); } wb_wccat(&pr->buffer, L']'); } if (pe->pe_type & PT_COLON) wb_wccat(&pr->buffer, L':'); switch (pe->pe_type & PT_MASK) { wchar_t c; case PT_PLUS: c = L'+'; goto append_subst; case PT_MINUS: c = L'-'; goto append_subst; case PT_ASSIGN: c = L'='; goto append_subst; case PT_ERROR: c = L'?'; goto append_subst; case PT_MATCH: if (pe->pe_type & PT_MATCHHEAD) { c = L'#'; } else { assert(pe->pe_type & PT_MATCHTAIL); c = L'%'; } wb_wccat(&pr->buffer, c); if (pe->pe_type & PT_MATCHLONGEST) wb_wccat(&pr->buffer, c); print_word(pr, pe->pe_match, indent); break; case PT_SUBST: wb_wccat(&pr->buffer, L'/'); if (pe->pe_type & PT_SUBSTALL) wb_wccat(&pr->buffer, L'/'); else if (pe->pe_type & PT_MATCHHEAD) wb_wccat(&pr->buffer, L'#'); else if (pe->pe_type & PT_MATCHTAIL) wb_wccat(&pr->buffer, L'%'); print_word(pr, pe->pe_match, indent); c = L'/'; append_subst: wb_wccat(&pr->buffer, c); print_word(pr, pe->pe_subst, indent); break; } wb_wccat(&pr->buffer, L'}'); } void print_embedded_command(struct print *pr, embedcmd_T ec, unsigned indent) { if (!ec.is_preparsed) { wb_cat(&pr->buffer, ec.value.unparsed); return; } size_t save_count = pr->pending_heredocs.length; void *save_heredocs[save_count]; memcpy(save_heredocs, pr->pending_heredocs.contents, sizeof save_heredocs); pl_clear(&pr->pending_heredocs, 0); print_and_or_lists(pr, ec.value.preparsed, indent, true); if (pr->multiline) { if (pr->pending_heredocs.length > 0) { print_space_or_newline(pr); // print remaining heredocs print_indent(pr, indent); } } else { pl_clear(&pr->pending_heredocs, 0); } assert(pr->pending_heredocs.length == 0); pl_ncat(&pr->pending_heredocs, save_heredocs, save_count); } void print_indent(struct print *pr, unsigned indent) { #ifndef FORMAT_INDENT_WIDTH #define FORMAT_INDENT_WIDTH 3 #endif if (pr->multiline) { xwcsbuf_T *buf = &pr->buffer; if (buf->length > 0 && buf->contents[buf->length - 1] != L'\n') return; indent *= FORMAT_INDENT_WIDTH; while (indent > 0) { wb_wccat(buf, L' '); indent--; } } } void print_space_or_newline(struct print* pr) { if (pr->multiline) { wb_wccat(&pr->buffer, L'\n'); print_pending_heredocs(pr); } else { wb_wccat(&pr->buffer, L' '); } } void print_pending_heredocs(struct print* pr) { for (size_t i = 0; i < pr->pending_heredocs.length; i++) { const redir_T *rd = pr->pending_heredocs.contents[i]; print_word(pr, rd->rd_herecontent, 0); wb_catfree(&pr->buffer, unquote(rd->rd_hereend)); wb_wccat(&pr->buffer, L'\n'); } pl_clear(&pr->pending_heredocs, 0); } void trim_end_of_buffer(xwcsbuf_T *buf) { size_t i = buf->length; while (i > 0 && buf->contents[i - 1] == L' ') i--; wb_truncate(buf, i); } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/parser.h000066400000000000000000000337131354143602500140730ustar00rootroot00000000000000/* Yash: yet another shell */ /* parser.h: syntax parser */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* Limitation: Don't include "parser.h" and at a time * because identifiers prefixed "c_" conflict. */ #ifndef YASH_PARSER_H #define YASH_PARSER_H #include #include "input.h" #include "refcount.h" /********** Parse Tree Structures **********/ /* Basically, parse tree structure elements constitute linked lists. * For each element, the `next' member points to the next element. */ /* and/or list */ typedef struct and_or_T { struct and_or_T *next; struct pipeline_T *ao_pipelines; /* pipelines in this and/or list */ _Bool ao_async; } and_or_T; /* ao_async: indicates this and/or list is postfixed by "&", which means the * list is executed asynchronously. */ /* pipeline */ typedef struct pipeline_T { struct pipeline_T *next; struct command_T *pl_commands; /* commands in this pipeline */ _Bool pl_neg, pl_cond; } pipeline_T; /* pl_neg: indicates this pipeline is prefix by "!", in which case the exit * status of the pipeline is inverted. * pl_cond: true if prefixed by "&&", false by "||". Ignored for the first * pipeline in an and/or list. */ /* type of command_T */ typedef enum { CT_SIMPLE, /* simple command */ CT_GROUP, /* command group enclosed by { } */ CT_SUBSHELL, /* subshell command group enclosed by ( ) */ CT_IF, /* if command */ CT_FOR, /* for command */ CT_WHILE, /* while/until command */ CT_CASE, /* case command */ #if YASH_ENABLE_DOUBLE_BRACKET CT_BRACKET, /* double-bracket command */ #endif CT_FUNCDEF, /* function definition */ } commandtype_T; /* command in a pipeline */ typedef struct command_T { struct command_T *next; refcount_T refcount; commandtype_T c_type; unsigned long c_lineno; /* line number */ struct redir_T *c_redirs; /* redirections */ union { struct { struct assign_T *assigns; /* assignments */ void **words; /* command name and arguments */ } simplecommand; struct and_or_T *subcmds; /* contents of command group */ struct ifcommand_T *ifcmds; /* contents of if command */ struct { wchar_t *forname; /* loop variable of for loop */ void **forwords; /* words assigned to loop variable */ struct and_or_T *forcmds; /* commands executed in for loop */ } forloop; struct { _Bool whltype; /* 1 for while loop, 0 for until */ struct and_or_T *whlcond; /* loop condition of while/until loop */ struct and_or_T *whlcmds; /* commands executed in loop */ } whileloop; struct { struct wordunit_T *casword; /* word compared to case patterns */ struct caseitem_T *casitems; /* pairs of patterns and commands */ } casecommand; struct dbexp_T *dbexp; /* double-bracket command expression */ struct { struct wordunit_T *funcname; /* name of function */ struct command_T *funcbody; /* body of function */ } funcdef; } c_content; } command_T; #define c_assigns c_content.simplecommand.assigns #define c_words c_content.simplecommand.words #define c_subcmds c_content.subcmds #define c_ifcmds c_content.ifcmds #define c_forname c_content.forloop.forname #define c_forwords c_content.forloop.forwords #define c_forcmds c_content.forloop.forcmds #define c_whltype c_content.whileloop.whltype #define c_whlcond c_content.whileloop.whlcond #define c_whlcmds c_content.whileloop.whlcmds #define c_casword c_content.casecommand.casword #define c_casitems c_content.casecommand.casitems #define c_dbexp c_content.dbexp #define c_funcname c_content.funcdef.funcname #define c_funcbody c_content.funcdef.funcbody /* `c_words' and `c_forwords' are NULL-terminated arrays of pointers to * `wordunit_T' that are cast to `void *'. * If `c_forwords' is NULL, the for loop doesn't have the "in" clause. * If `c_forwords[0]' is NULL, the "in" clause exists and is empty. */ /* condition and commands of an if command */ typedef struct ifcommand_T { struct ifcommand_T *next; struct and_or_T *ic_condition; /* condition */ struct and_or_T *ic_commands; /* commands */ } ifcommand_T; /* For an "else" clause, `next' and `ic_condition' are NULL. */ /* patterns and commands of a case command */ typedef struct caseitem_T { struct caseitem_T *next; void **ci_patterns; /* patterns to do matching */ struct and_or_T *ci_commands; /* commands executed if match succeeds */ } caseitem_T; /* `ci_patterns' is a NULL-terminated array of pointers to `wordunit_T' that are * cast to `void *'. */ /* type of dbexp_T */ typedef enum { DBE_OR, /* the "||" operator, two operand expressions */ DBE_AND, /* the "&&" operator, two operand expressions */ DBE_NOT, /* the "!" operator, one operand expression */ DBE_UNARY, /* -f, -n, etc., one operand word */ DBE_BINARY, /* -eq, =, etc., two operand words */ DBE_STRING, /* single string primary, one operand word */ } dbexptype_T; /* operand of expression in double-bracket command */ typedef union dboperand_T { struct dbexp_T *subexp; struct wordunit_T *word; } dboperand_T; /* expression in double-bracket command */ typedef struct dbexp_T { dbexptype_T type; wchar_t *operator; dboperand_T lhs, rhs; } dbexp_T; /* `operator' is NULL for non-primary expressions */ /* `lhs' is NULL for one-operand expressions */ /* embedded command */ typedef struct embedcmd_T { _Bool is_preparsed; union { wchar_t *unparsed; struct and_or_T *preparsed; } value; } embedcmd_T; /* type of wordunit_T */ typedef enum { WT_STRING, /* string (including quotes) */ WT_PARAM, /* parameter expansion */ WT_CMDSUB, /* command substitution */ WT_ARITH, /* arithmetic expansion */ } wordunittype_T; /* element of a word subject to expansion */ typedef struct wordunit_T { struct wordunit_T *next; wordunittype_T wu_type; union { wchar_t *string; /* string (including quotes) */ struct paramexp_T *param; /* parameter expansion */ struct embedcmd_T cmdsub; /* command substitution */ struct wordunit_T *arith; /* expression for arithmetic expansion */ } wu_value; } wordunit_T; #define wu_string wu_value.string #define wu_param wu_value.param #define wu_cmdsub wu_value.cmdsub #define wu_arith wu_value.arith /* In arithmetic expansion, the expression is subject to parameter expansion * before it is parsed. So `wu_arith' is of type `wordunit_T *'. */ /* type of paramexp_T */ typedef enum { PT_NONE, /* normal */ PT_MINUS, /* ${name-subst} */ PT_PLUS, /* ${name+subst} */ PT_ASSIGN, /* ${name=subst} */ PT_ERROR, /* ${name?subst} */ PT_MATCH, /* ${name#match}, ${name%match} */ PT_SUBST, /* ${name/match/subst} */ PT_MASK = (1 << 3) - 1, PT_NUMBER = 1 << 3, /* ${#name} (only valid for PT_NONE) */ PT_COLON = 1 << 4, /* ${name:-subst}, ${name:+subst}, etc. */ PT_MATCHHEAD = 1 << 5, /* only matches at the beginning of a string */ PT_MATCHTAIL = 1 << 6, /* only matches at the end of a string */ PT_MATCHLONGEST = 1 << 7, /* match as long as possible */ PT_SUBSTALL = 1 << 8, /* substitute all the match */ PT_NEST = 1 << 9, /* have nested expn. like ${${VAR#foo}%bar} */ } paramexptype_T; /* type COLON MATCHH MATCHT MATCHL SUBSTA * ${n-s} MINUS no * ${n+s} PLUS no * ${n=s} ASSIGN no * ${n?s} ERROR no * ${n:-s} MINUS yes * ${n:+s} PLUS yes * ${n:=s} ASSIGN yes * ${n:?s} ERROR yes * ${n#m} MATCH no yes no no no * ${n##m} MATCH no yes no yes no * ${n%m} MATCH no no yes no no * ${n%%m} MATCH no no yes yes no * ${n/m/s} SUBST no no no yes no * ${n/#m/s} SUBST no yes no yes no * ${n/%m/s} SUBST no no yes yes no * ${n//m/s} SUBST no no no yes yes * ${n:/m/s} SUBST yes yes yes * * PT_SUBST and PT_NEST is beyond POSIX. */ /* parameter expansion */ typedef struct paramexp_T { paramexptype_T pe_type; union { wchar_t *name; struct wordunit_T *nest; } pe_value; struct wordunit_T *pe_start, *pe_end; struct wordunit_T *pe_match, *pe_subst; } paramexp_T; #define pe_name pe_value.name #define pe_nest pe_value.nest /* pe_name: name of parameter * pe_nest: nested parameter expansion * pe_start: index of the first element in the range * pe_end: index of the last element in the range * pe_match: word to be matched with the value of the parameter * pe_subst: word to to substitute the matched string with * `pe_start' and `pe_end' is NULL if the indices are not specified. * `pe_match' and `pe_subst' may be NULL to denote an empty string. */ /* type of assignment */ typedef enum { A_SCALAR, A_ARRAY, } assigntype_T; /* assignment */ typedef struct assign_T { struct assign_T *next; assigntype_T a_type; wchar_t *a_name; union { struct wordunit_T *scalar; void **array; } a_value; /* value to assign */ } assign_T; #define a_scalar a_value.scalar #define a_array a_value.array /* `a_scalar' may be NULL to denote an empty string. * `a_array' is an array of pointers to `wordunit_T'. */ /* type of redirection */ typedef enum { RT_INPUT, /* file */ RT_CLOBBER, /* >|file */ RT_APPEND, /* >>file */ RT_INOUT, /* <>file */ RT_DUPIN, /* <&fd */ RT_DUPOUT, /* >&fd */ RT_PIPE, /* >>|fd */ RT_HERE, /* <(command) */ } redirtype_T; /* redirection */ typedef struct redir_T { struct redir_T *next; redirtype_T rd_type; int rd_fd; /* file descriptor to redirect */ union { struct wordunit_T *filename; struct { wchar_t *hereend; /* token indicating end of here-document */ struct wordunit_T *herecontent; /* contents of here-document */ } heredoc; struct embedcmd_T command; } rd_value; } redir_T; #define rd_filename rd_value.filename #define rd_hereend rd_value.heredoc.hereend #define rd_herecontent rd_value.heredoc.herecontent #define rd_command rd_value.command /* For example, for "2>&1", `rd_type' = RT_DUPOUT, `rd_fd' = 2 and * `rd_filename' = "1". * For RT_HERERT, all the lines in `rd_herecontent' have the leading tabs * already removed. If `rd_hereend' is quoted, `rd_herecontent' is a single * word unit of type WT_STRING, since no parameter expansions are performed. * Anyway `rd_herecontent' is expanded by calling `expand_string' with `esc' * argument being true. */ /********** Interface to Parsing Routines **********/ /* Holds parameters that affect the behavior of parsing. */ typedef struct parseparam_T { _Bool print_errmsg; /* print error messages? */ _Bool enable_verbose; /* echo input if `shopt_verbose' is true? */ _Bool enable_alias; /* perform alias substitution? */ const char *filename; /* the input filename, which may be NULL */ unsigned long lineno; /* line number, which should be initialized to 1 */ inputfunc_T *input; /* input function */ void *inputinfo; /* pointer passed to the input function */ _Bool interactive; /* input is interactive? */ inputresult_T lastinputresult; /* last return value of input function */ } parseparam_T; /* If `interactive' is true, `input' is `input_interactive' and `inputinfo' is a * pointer to a `struct input_interactive_info_T' object. * Note that input may not be from a terminal even if `interactive' is true. */ typedef enum parseresult_T { PR_OK, PR_EOF, PR_SYNTAX_ERROR, PR_INPUT_ERROR, } parseresult_T; extern parseresult_T read_and_parse( parseparam_T *info, and_or_T **restrict resultp) __attribute__((nonnull,warn_unused_result)); extern _Bool parse_string(parseparam_T *info, wordunit_T **restrict resultp) __attribute__((nonnull,warn_unused_result)); /********** Auxiliary Functions **********/ extern _Bool is_portable_name_char(wchar_t c) __attribute__((const)); extern _Bool is_name_char(wchar_t c) __attribute__((pure)); extern _Bool is_name(const wchar_t *s) __attribute__((pure)); extern _Bool is_keyword(const wchar_t *s) __attribute__((nonnull,pure)); extern _Bool is_token_delimiter_char(wchar_t c) __attribute__((pure)); /********** Functions That Convert Parse Trees into Strings **********/ extern wchar_t *pipelines_to_wcs(const pipeline_T *pipelines) __attribute__((malloc,warn_unused_result)); extern wchar_t *command_to_wcs(const command_T *command, _Bool multiline) __attribute__((malloc,warn_unused_result)); /********** Functions That Free/Duplicate Parse Trees **********/ extern void andorsfree(and_or_T *a); static inline command_T *comsdup(command_T *c); extern void comsfree(command_T *c); extern void wordfree(wordunit_T *w); extern void paramfree(paramexp_T *p); /* Duplicates the specified command (virtually). */ command_T *comsdup(command_T *c) { refcount_increment(&c->refcount); return c; } #endif /* YASH_PARSER_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/path.c000066400000000000000000001377541354143602500135400ustar00rootroot00000000000000/* Yash: yet another shell */ /* path.c: filename-related utilities */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "path.h" #include #include #include #include #include #include #if HAVE_GETTEXT # include #endif #include #if HAVE_PATHS_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "exec.h" #include "expand.h" #include "hashtable.h" #include "option.h" #include "plist.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" #include "xfnmatch.h" #include "yash.h" #if HAVE_FACCESSAT # ifndef faccessat extern int faccessat(int fd, const char *path, int amode, int flags) __attribute__((nonnull)); # endif #elif HAVE_EACCESS # ifndef eaccess extern int eaccess(const char *path, int amode) __attribute__((nonnull)); # endif #endif static bool check_access(const char *path, mode_t mode, int amode) __attribute__((nonnull)); /* Checks if `path' is an existing file. */ bool is_file(const char *path) { return access(path, F_OK) == 0; // struct stat st; // return (stat(path, &st) == 0); } /* Checks if `path' is a regular file. */ bool is_regular_file(const char *path) { struct stat st; return (stat(path, &st) == 0) && S_ISREG(st.st_mode); } /* Checks if `path' is a non-regular file. */ bool is_irregular_file(const char *path) { struct stat st; return (stat(path, &st) == 0) && !S_ISREG(st.st_mode); } /* Checks if `path' is a readable file. */ bool is_readable(const char *path) { return check_access(path, S_IRUSR | S_IRGRP | S_IROTH, R_OK); } /* Checks if `path' is a writable file. */ bool is_writable(const char *path) { return check_access(path, S_IWUSR | S_IWGRP | S_IWOTH, W_OK); } /* Checks if `path' is an executable file (or a searchable directory). */ bool is_executable(const char *path) { return check_access(path, S_IXUSR | S_IXGRP | S_IXOTH, X_OK); } /* Checks if this process has a proper permission to access the specified file. * Returns false if the file does not exist. */ bool check_access(const char *path, mode_t mode, int amode) { /* Even if the faccessat/eaccess function was considered available by * `configure', the OS kernel may not support it. We fall back on our own * checking function if faccessat/eaccess was rejected. */ #if HAVE_FACCESSAT if (faccessat(AT_FDCWD, path, amode, AT_EACCESS) == 0) return true; if (errno != ENOSYS && errno != EINVAL) return false; #elif HAVE_EACCESS if (eaccess(path, amode) == 0) return true; if (errno != ENOSYS && errno != EINVAL) return false; #else (void) amode; #endif /* The algorithm below is not 100% valid for all POSIX systems. */ struct stat st; uid_t uid; gid_t gid; if (stat(path, &st) < 0) return false; uid = geteuid(); #if !YASH_DISABLE_SUPERUSER if (uid == 0) { /* the "root" user has special permissions */ return (mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH)) || S_ISDIR(st.st_mode) || (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); } #endif st.st_mode &= mode; if (uid == st.st_uid) return st.st_mode & S_IRWXU; gid = getegid(); if (gid == st.st_gid) return st.st_mode & S_IRWXG; int gcount = getgroups(0, &gid); /* the second argument is a dummy */ if (gcount > 0) { gid_t groups[gcount]; gcount = getgroups(gcount, groups); if (gcount > 0) { for (int i = 0; i < gcount; i++) if (gid == groups[i]) return st.st_mode & S_IRWXG; } } return st.st_mode & S_IRWXO; } /* Checks if `path' is a readable regular file. */ bool is_readable_regular(const char *path) { return is_regular_file(path) && is_readable(path); } /* Checks if `path' is an executable regular file. */ bool is_executable_regular(const char *path) { return is_regular_file(path) && is_executable(path); } /* Checks if `path' is a directory. */ bool is_directory(const char *path) { struct stat st; return (stat(path, &st) == 0) && S_ISDIR(st.st_mode); } /* Checks if two stat results name the same file. */ inline bool stat_result_same_file( const struct stat *stat1, const struct stat *stat2) { return stat1->st_dev == stat2->st_dev && stat1->st_ino == stat2->st_ino; } /* Checks if two files are the same file. */ bool is_same_file(const char *path1, const char *path2) { struct stat stat1, stat2; return stat(path1, &stat1) == 0 && stat(path2, &stat2) == 0 && stat_result_same_file(&stat1, &stat2); } /* Checks if the specified `path' is normalized, that is, containing no "." * or ".." in it. */ /* Note that a normalized path may contain redundant slashes. */ bool is_normalized_path(const wchar_t *path) { while (path[0] != L'\0') { if (path[0] == L'.' && (path[1] == L'\0' || path[1] == L'/' || (path[1] == L'.' && (path[2] == L'\0' || path[2] == L'/')))) return false; path = wcschr(path, L'/'); if (path == NULL) break; path++; } return true; } /* Returns the result of `getcwd' as a newly malloced string. * On error, `errno' is set and NULL is returned. */ char *xgetcwd(void) { #if GETCWD_AUTO_MALLOC char *pwd = getcwd(NULL, 0); return (pwd != NULL) ? xrealloc(pwd, add(strlen(pwd), 1)) : NULL; #else size_t pwdlen = 40; char *pwd = xmalloc(pwdlen); while (getcwd(pwd, pwdlen) == NULL) { if (errno == ERANGE) { pwdlen = mul(pwdlen, 2); pwd = xrealloc(pwd, pwdlen); } else { int saveerrno = errno; free(pwd); pwd = NULL; errno = saveerrno; break; } } return pwd; #endif } /* Searches directories `dirs' for a file named `name' that satisfies predicate * `cond'. * name: the pathname of the file to be searched for. * If `name' is an absolute path, a copy of it is simply returned. * dirs: a NULL-terminated array of strings that are the names of * directories to search. An empty string is treated as the current * directory. If `dirs' is NULL, no directory is searched. * cond: the function that determines the specified pathname satisfies a * certain condition. * For each directory in `dirs', in order, the directory name and "/" and * `name' are concatenated to produce a pathname and `cond' is called with * the pathname. If `cond' returns true, search is complete and the pathname * is returned as a newly malloced string. If `cond' returns false to all the * produced pathnames, NULL is returned. * If `name' starts with a slash, a copy of `name' is simply returned. If `name' * is an empty string or `dirs' is NULL, NULL is returned. * `name' and all the directory names in `dirs' must start and end in the * initial shift state. */ char *which( const char *restrict name, char *const *restrict dirs, bool cond(const char *path)) { if (name[0] == L'\0') return NULL; if (name[0] == '/') return xstrdup(name); if (dirs == NULL) return NULL; size_t namelen = strlen(name); for (const char *dir; (dir = *dirs) != NULL; dirs++) { size_t dirlen = strlen(dir); char path[dirlen + namelen + 3]; if (dirlen > 0) { /* concatenate `dir' and `name' to produce a pathname `path' */ strcpy(path, dir); if (path[dirlen - 1] != '/') path[dirlen++] = '/'; strcpy(path + dirlen, name); } else { /* if `dir' is empty, it's considered to be the current directory */ strcpy(path, name); } if (cond(path)) return xstrdup(path); } return NULL; } /* Creates a new temporary file under "/tmp". * `mode' is the access permission bits of the file. * The name of the temporary file ends with `suffix`, which should be as short * as possible and must not exceed 8 bytes. * On successful completion, a file descriptor is returned, which is both * readable and writeable regardless of `mode', and a pointer to a string * containing the filename is assigned to `*filename', which should be freed by * the caller. The filename consists only of portable filename characters. * On failure, -1 is returned with `**filename' left unchanged and `errno' is * set to the error value. */ int create_temporary_file( char **restrict filename, const char *restrict suffix, mode_t mode) { static uintmax_t num = 0; uintmax_t n; int fd; xstrbuf_T buf; n = (uintmax_t) shell_pid * 272229637312669; if (num == 0) num = (uintmax_t) time(NULL) * 5131212142718371 << 1 | 1; sb_init(&buf); for (int i = 0; i < 100; i++) { num = (num ^ n) * 16777619; sb_printf(&buf, "/tmp/yash-%" PRIXMAX, num); size_t maxlen = _POSIX_NAME_MAX + 5 - strlen(suffix); if (buf.length > maxlen) sb_truncate(&buf, maxlen); sb_cat(&buf, suffix); fd = open(buf.contents, O_RDWR | O_CREAT | O_EXCL, mode); if (fd >= 0) { *filename = sb_tostr(&buf); return fd; } else if (errno != EEXIST && errno != EINTR) { int saveerrno = errno; sb_destroy(&buf); errno = saveerrno; return -1; } sb_clear(&buf); } sb_destroy(&buf); errno = EAGAIN; return -1; } /********** Command Hashtable **********/ static inline void forget_command_path(const char *command) __attribute__((nonnull)); static wchar_t *get_default_path(void) __attribute__((malloc,warn_unused_result)); /* A hashtable from command names to their full path. * Keys are pointers to a multibyte string containing a command name and * values are pointers to a multibyte string containing the commands' full path. * For each entry, the key string is part of the value, that is, the last * pathname component of the value. * Full paths may be relative, in which case the paths are unreliable because * the working directory may have been changed since the paths had been * entered. */ static hashtable_T cmdhash; /* Initializes the command hashtable. */ void init_cmdhash(void) { assert(cmdhash.capacity == 0); ht_init(&cmdhash, hashstr, htstrcmp); } /* Empties the command hashtable. */ void clear_cmdhash(void) { ht_clear(&cmdhash, vfree); } /* Searches PATH for the specified command and returns its full pathname. * If `forcelookup' is false and the command is already entered in the command * hashtable, the value in the hashtable is returned. Otherwise, `which' is * called to search for the command, the result is entered into the hashtable, * and then it is returned. If no command is found, NULL is returned. */ const char *get_command_path(const char *name, bool forcelookup) { const char *path; if (!forcelookup) { path = ht_get(&cmdhash, name).value; if (path != NULL && path[0] == '/' && is_executable_regular(path)) return path; } path = which(name, get_path_array(PA_PATH), is_executable_regular); if (path != NULL) { size_t namelen = strlen(name), pathlen = strlen(path); const char *nameinpath = path + pathlen - namelen; assert(strcmp(name, nameinpath) == 0); vfree(ht_set(&cmdhash, nameinpath, path)); } else { forget_command_path(name); } return path; } /* Removes the specified command from the command hashtable. */ void forget_command_path(const char *command) { vfree(ht_remove(&cmdhash, command)); } /* Last result of `get_command_path_default'. */ static char *gcpd_value = NULL; /* Paths for `get_command_path_default'. */ static char **default_path = NULL; /* Searches for the specified command using the system's default PATH. * The full path of the command is returned if found, NULL otherwise. * The return value is valid until the next call to this function. */ const char *get_command_path_default(const char *name) { assert(name != gcpd_value); free(gcpd_value); if (default_path == NULL) { wchar_t *defpath = get_default_path(); if (defpath == NULL) { gcpd_value = NULL; return gcpd_value; } default_path = decompose_paths(defpath); free(defpath); } gcpd_value = which(name, default_path, is_executable_regular); return gcpd_value; } /* Returns the system's default PATH as a newly malloced string. * The default PATH is (assumed to be) guaranteed to contain all the standard * utilities. */ wchar_t *get_default_path(void) { #if HAVE_PATHS_H && defined _PATH_STDPATH return malloc_mbstowcs(_PATH_STDPATH); #else size_t size = 100; char *buf = xmalloc(size); size_t s = confstr(_CS_PATH, buf, size); if (s > size) { size = s; buf = xrealloc(buf, size); s = confstr(_CS_PATH, buf, size); } if (s == 0 || s > size) { free(buf); return NULL; } return realloc_mbstowcs(buf); #endif } /********** Home Directory Cache **********/ static struct passwd *xgetpwnam(const char *name) __attribute__((nonnull)); static void clear_homedirhash(void); static inline void forget_home_directory(const wchar_t *username); /* Calls `getpwnam' until it doesn't return EINTR. */ struct passwd *xgetpwnam(const char *name) { struct passwd *pw; do { errno = 0; pw = getpwnam(name); } while (pw == NULL && errno == EINTR); return pw; } /* A hashtable from users' names to their home directory paths. * Keys are pointers to a wide string containing a user's login name and * values are pointers to a wide string containing their home directory name. * A memory block for the key/value string must be allocated at once so that, * when the value is `free'd, the key is `free'd as well. */ static hashtable_T homedirhash; /* Initializes the home directory hashtable. */ void init_homedirhash(void) { assert(homedirhash.capacity == 0); ht_init(&homedirhash, hashwcs, htwcscmp); } /* Empties the home directory hashtable. */ void clear_homedirhash(void) { ht_clear(&homedirhash, vfree); } /* Returns the full pathname of the specified user's home directory. * If `forcelookup' is false and the path is already entered in the home * directory hashtable, the value in the hashtable is returned. Otherwise, * `getpwnam' is called, the result is entered into the hashtable and then * it is returned. If no entry is returned by `getpwnam', NULL is returned. */ const wchar_t *get_home_directory(const wchar_t *username, bool forcelookup) { const wchar_t *path; if (!forcelookup) { path = ht_get(&homedirhash, username).value; if (path != NULL) return path; } char *mbsusername = malloc_wcstombs(username); if (mbsusername == NULL) return NULL; struct passwd *pw = xgetpwnam(mbsusername); free(mbsusername); if (pw == NULL) return NULL; xwcsbuf_T dir; wb_init(&dir); if (wb_mbscat(&dir, pw->pw_dir) != NULL) { wb_destroy(&dir); return NULL; } wb_wccat(&dir, L'\0'); size_t usernameindex = dir.length; wb_cat(&dir, username); wchar_t *dirname = wb_towcs(&dir); vfree(ht_set(&homedirhash, dirname + usernameindex, dirname)); return dirname; } /* Forget the specified user's home directory. */ void forget_home_directory(const wchar_t *username) { vfree(ht_remove(&homedirhash, username)); } /********** wglob **********/ /* The type of compiled glob patterns */ struct wglob_pattern { struct wglob_pattern *next; enum { WGLOB_LITERAL, WGLOB_MATCH, WGLOB_RECSEARCH, } type; union { struct { char *name; const wchar_t *wname; } literal; struct { xfnmatch_T *pattern; } match; struct { bool followlink, allowperiod; } recsearch; } value; }; struct wglob_dirstack { struct wglob_dirstack *prev; struct stat st; }; static struct wglob_pattern *wglob_parse_pattern( wchar_t *pat, enum wglobflags_T flags) __attribute__((malloc,warn_unused_result,nonnull)); static struct wglob_pattern *wglob_create_recsearch_pattern( bool followlink, bool allowperiod) __attribute__((malloc,warn_unused_result)); static struct wglob_pattern *wglob_parse_pattern_part( wchar_t *pat, enum wglobflags_T flags) __attribute__((malloc,warn_unused_result,nonnull)); static void wglob_free_pattern(struct wglob_pattern *p); static void wglob_search( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) __attribute__((nonnull)); static void wglob_search_literal( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) __attribute__((nonnull)); static void wglob_search_match( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) __attribute__((nonnull)); static void wglob_search_recsearch( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list, struct wglob_dirstack *dirstack) __attribute__((nonnull(1,3,4,5))); static bool is_reentry( const struct stat *st, const struct wglob_dirstack *dirstack) __attribute__((nonnull(1))); static int wglob_sortcmp(const void *v1, const void *v2) __attribute__((pure,nonnull)); /* A wide string version of `glob'. * Adds all pathnames that matches the specified pattern to the specified list. * pattern: the pattern to match * flags: a bitwise OR of the following flags: * WGLB_MARK: directory items have '/' appended to their name * WGLB_CASEFOLD: do matching case-insensitively * WGLB_PERIOD: L'*' and L'?' match L'.' at the beginning * WGLB_NOSORT: don't sort resulting items * WGLB_RECDIR: allow recursive search with L"**" * list: a list of pointers to wide strings to which resulting items are * added. * Returns true iff successful. However, some result items may be added to the * list even if unsuccessful. * If the pattern is invalid, immediately returns false. * If the shell is interactive and SIGINT is not blocked, this function can be * interrupted, in which case false is returned. * Minor errors such as permission errors are ignored. */ bool wglob(const wchar_t *restrict pattern, enum wglobflags_T flags, plist_T *restrict list) { size_t listbase = list->length; xstrbuf_T path; xwcsbuf_T wpath; struct wglob_pattern *p; wchar_t savepattern[wcslen(pattern) + 1]; p = wglob_parse_pattern(wcscpy(savepattern, pattern), flags); if (p == NULL) return false; sb_init(&path); wb_init(&wpath); wglob_search(p, flags, &path, &wpath, list); sb_destroy(&path); wb_destroy(&wpath); wglob_free_pattern(p); if (!(flags & WGLB_NOSORT)) { size_t count = list->length - listbase; /* # of resulting items */ if (count > 0) { /* sort the items */ qsort(list->contents + listbase, count, sizeof (void *), wglob_sortcmp); /* remove duplicates */ for (size_t i = list->length; --i > listbase; ) { if (wcscmp(list->contents[i], list->contents[i-1]) == 0) { free(list->contents[i]); pl_remove(list, i, 1); } } } } return !is_interrupted(); } /* Parses the specified pattern. * Pattern `pat' may be modified in this function and must not be changed until * the return value is freed by `wglob_free_pattern'. * WGLB_CASEFOLD, WGLB_PERIOD and WGLB_RECDIR in `flags' affect the results. */ struct wglob_pattern *wglob_parse_pattern(wchar_t *pat, enum wglobflags_T flags) { struct wglob_pattern *result = NULL, **lastp = &result; struct wglob_pattern *p; for (;;) { wchar_t *slash = wcschr(pat, L'/'); if (slash != NULL) { slash[0] = L'\0'; if (!(flags & WGLB_RECDIR)) goto normal; if (wcscmp(pat, L"**") == 0) p = wglob_create_recsearch_pattern(false, false); else if (wcscmp(pat, L"***") == 0) p = wglob_create_recsearch_pattern(true, false); else if (wcscmp(pat, L".**") == 0) p = wglob_create_recsearch_pattern(false, true); else if (wcscmp(pat, L".***") == 0) p = wglob_create_recsearch_pattern(true, true); else goto normal; } else { normal: p = wglob_parse_pattern_part(pat, flags); } if (p == NULL) goto fail; *lastp = p; lastp = &p->next; if (slash == NULL) return result; pat = &slash[1]; } fail: wglob_free_pattern(result); return NULL; } struct wglob_pattern *wglob_create_recsearch_pattern( bool followlink, bool allowperiod) { struct wglob_pattern *result = xmalloc(sizeof *result); result->next = NULL; result->type = WGLOB_RECSEARCH; result->value.recsearch.followlink = followlink; result->value.recsearch.allowperiod = allowperiod; return result; } /* Parses the specified pattern that contains one pathname component. * `pat' must not contain a slash. */ struct wglob_pattern *wglob_parse_pattern_part( wchar_t *pat, enum wglobflags_T flags) { struct wglob_pattern *result = xmalloc(sizeof *result); result->next = NULL; assert(!wcschr(pat, L'/')); if (is_matching_pattern(pat)) { xfnmflags_T xflags = XFNM_HEADONLY | XFNM_TAILONLY; if (flags & WGLB_CASEFOLD) xflags |= XFNM_CASEFOLD; if (!(flags & WGLB_PERIOD)) xflags |= XFNM_PERIOD; result->type = WGLOB_MATCH; result->value.match.pattern = xfnm_compile(pat, xflags); if (result->value.match.pattern == NULL) goto fail; } else { wchar_t *value = unescape(pat); assert(wcslen(value) <= wcslen(pat)); result->type = WGLOB_LITERAL; result->value.literal.wname = wcscpy(pat, value); result->value.literal.name = realloc_wcstombs(value); if (result->value.literal.name == NULL) goto fail; } return result; fail: free(result); return NULL; } void wglob_free_pattern(struct wglob_pattern *p) { while (p != NULL) { struct wglob_pattern *next = p->next; switch (p->type) { case WGLOB_LITERAL: free(p->value.literal.name); break; case WGLOB_MATCH: xfnm_free(p->value.match.pattern); break; case WGLOB_RECSEARCH: break; } free(p); p = next; } } /* Searches the directory designated in `path' and add matching pathnames to * `list'. * `path' and `wpath' must contain the same pathname, which must be empty or * end with a slash. The contents of `path' and `wpath' may be changed during * the search, but when the function returns the contents are restored to the * original value. * Only the WGLB_MARK flag in `flags' affects the results. */ void wglob_search( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) { assert(path->length == 0 || path->contents[path->length - 1] == '/'); assert(wpath->length == 0 || wpath->contents[wpath->length - 1] == L'/'); switch (pattern->type) { case WGLOB_LITERAL: wglob_search_literal(pattern, flags, path, wpath, list); break; case WGLOB_MATCH: wglob_search_match(pattern, flags, path, wpath, list); break; case WGLOB_RECSEARCH: wglob_search_recsearch(pattern, flags, path, wpath, list, NULL); break; } } /* Searches for the pathname component that does not require pattern matching.*/ void wglob_search_literal( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) { const size_t savepathlen = path->length; const size_t savewpathlen = wpath->length; assert(pattern->type == WGLOB_LITERAL); if (pattern->next == NULL) { struct stat st; sb_cat(path, pattern->value.literal.name); if (stat(path->contents, &st) >= 0) { if (pattern->value.literal.wname[0] != L'\0') { wb_cat(wpath, pattern->value.literal.wname); if ((flags & WGLB_MARK) && S_ISDIR(st.st_mode)) wb_wccat(wpath, L'/'); } pl_add(list, xwcsdup(wpath->contents)); } } else { sb_ccat(sb_cat(path, pattern->value.literal.name), '/'); wb_wccat(wb_cat(wpath, pattern->value.literal.wname), L'/'); wglob_search(pattern->next, flags, path, wpath, list); } sb_truncate(path, savepathlen); wb_truncate(wpath, savewpathlen); } /* Searches the directory for files that match with the specified pattern. */ void wglob_search_match( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list) { assert(pattern->type == WGLOB_MATCH); if (is_interrupted()) return; DIR *dir = opendir((path->length == 0) ? "." : path->contents); if (dir == NULL) return; const size_t savepathlen = path->length; const size_t savewpathlen = wpath->length; struct dirent *de; while ((de = readdir(dir)) != NULL) { if (xfnm_match(pattern->value.match.pattern, de->d_name) == 0) { if (wb_mbscat(wpath, de->d_name) != NULL) goto next; sb_cat(path, de->d_name); if (pattern->next == NULL) { if (flags & WGLB_MARK) { if (is_directory(path->contents)) wb_wccat(wpath, L'/'); } pl_add(list, xwcsdup(wpath->contents)); } else { sb_ccat(path, '/'); wb_wccat(wpath, L'/'); wglob_search(pattern->next, flags, path, wpath, list); } next: sb_truncate(path, savepathlen); wb_truncate(wpath, savewpathlen); } } closedir(dir); } /* Searches the directory recursively for files that match with the specified * pattern. */ void wglob_search_recsearch( const struct wglob_pattern *restrict pattern, enum wglobflags_T flags, xstrbuf_T *restrict path, xwcsbuf_T *restrict wpath, plist_T *restrict list, struct wglob_dirstack *dirstack) { assert(pattern->type == WGLOB_RECSEARCH); assert(pattern->next != NULL); if (is_interrupted()) return; /* Step 1: search `path' itself */ wglob_search(pattern->next, flags, path, wpath, list); /* Step 2: search the subdirectories of `path' recursively */ DIR *dir = opendir((path->length == 0) ? "." : path->contents); if (dir == NULL) return; const size_t savepathlen = path->length; const size_t savewpathlen = wpath->length; struct dirent *de; int (*statfunc)(const char *path, struct stat *st) = pattern->value.recsearch.followlink ? stat : lstat; while ((de = readdir(dir)) != NULL) { if (pattern->value.recsearch.allowperiod ? strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 : de->d_name[0] == '.') continue; struct wglob_dirstack newstack; sb_cat(path, de->d_name); if (statfunc(path->contents, &newstack.st) >= 0 && S_ISDIR(newstack.st.st_mode) && !is_reentry(&newstack.st, dirstack)) { if (wb_mbscat(wpath, de->d_name) != NULL) goto next; sb_ccat(path, '/'); wb_wccat(wpath, L'/'); newstack.prev = dirstack; wglob_search_recsearch( pattern, flags, path, wpath, list, &newstack); } next: sb_truncate(path, savepathlen); wb_truncate(wpath, savewpathlen); } closedir(dir); } /* Returns true iff the file designated by `st' is contained in `dirstack'. */ bool is_reentry(const struct stat *st, const struct wglob_dirstack *dirstack) { for (; dirstack != NULL; dirstack = dirstack->prev) if (stat_result_same_file(st, &dirstack->st)) return true; return false; } int wglob_sortcmp(const void *v1, const void *v2) { return wcscoll(*(const wchar_t *const *) v1, *(const wchar_t *const *) v2); } /********** Built-ins **********/ static wchar_t *canonicalize_path(const wchar_t *path) __attribute__((nonnull,malloc,warn_unused_result)); static inline bool not_dotdot(const wchar_t *p) __attribute__((nonnull,pure)); static void canonicalize_path_ex(xwcsbuf_T *buf) __attribute__((nonnull)); static bool starts_with_root_parent(const wchar_t *path) __attribute__((nonnull,pure)); static void print_command_paths(bool all); static void print_home_directories(void); static int print_umask(bool symbolic); static inline bool print_umask_octal(mode_t mode); static bool print_umask_symbolic(mode_t mode); static int set_umask(const wchar_t *newmask) __attribute__((nonnull)); static inline mode_t copy_user_mask(mode_t mode) __attribute__((const)); static inline mode_t copy_group_mask(mode_t mode) __attribute__((const)); static inline mode_t copy_other_mask(mode_t mode) __attribute__((const)); /* The "cd" built-in, which accepts the following options: * -L: don't resolve symbolic links (default) * -P: resolve symbolic links * --default-directory=: go to when the operand is missing * -L and -P are mutually exclusive: the one specified last is used. */ int cd_builtin(int argc, void **argv) { bool logical = true; const wchar_t *newpwd = NULL; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, cd_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'L': logical = true; break; case L'P': logical = false; break; case L'd': newpwd = xoptarg; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } bool printnewdir = false; switch (argc - xoptind) { case 0: if (newpwd == NULL) { newpwd = getvar(L VAR_HOME); if (newpwd == NULL || newpwd[0] == L'\0') { xerror(0, Ngt("$HOME is not set")); return Exit_FAILURE; } } break; case 1: if (wcscmp(ARGV(xoptind), L"-") == 0) { newpwd = getvar(L VAR_OLDPWD); if (newpwd == NULL || newpwd[0] == L'\0') { xerror(0, Ngt("$OLDPWD is not set")); return Exit_FAILURE; } printnewdir = true; } else { newpwd = ARGV(xoptind); } break; default: return too_many_operands_error(1); } return change_directory(newpwd, printnewdir, logical); } /* Changes the working directory to `newpwd'. * This function implements the almost all part of the "cd" built-in. * $PWD and $OLDPWD are set in this function. * If `printnewdir' is true or the new directory is found from $CDPATH, the new * directory is printed to the standard output. * Returns Exit_SUCCESS, Exit_FAILURE or Exit_ERROR. */ int change_directory(const wchar_t *newpwd, bool printnewdir, bool logical) { const wchar_t *origpwd; xwcsbuf_T curpath; size_t curpathoffset = 0; /* get the current value of $PWD as `origpwd' */ origpwd = getvar(L VAR_PWD); if (origpwd == NULL || origpwd[0] != L'/') { if (origpwd == newpwd) { xerror(0, Ngt("$PWD has an invalid value")); return Exit_FAILURE; } /* we have to assure `origpwd != newpwd' because we're going to * re-assign $PWD */ char *pwd = xgetcwd(); if (pwd == NULL) { if (logical) { xerror(errno, Ngt("cannot determine the current directory")); return Exit_FAILURE; } } else { wchar_t *wpwd = realloc_mbstowcs(pwd); if (wpwd != NULL) { if (set_variable(L VAR_PWD, wpwd, SCOPE_GLOBAL, false)) origpwd = getvar(L VAR_PWD); else logical = false, origpwd = NULL; } else { xerror(EILSEQ, Ngt("cannot determine the current directory")); return Exit_ERROR; } } } assert(!logical || origpwd != NULL); assert(origpwd == NULL || origpwd[0] == L'/'); wb_init(&curpath); /* step 3 */ if (newpwd[0] == L'/') { wb_cat(&curpath, newpwd); goto step7; } /* step 4: check if `newpwd' starts with "." or ".." */ if (newpwd[0] == L'.' && (newpwd[1] == L'\0' || newpwd[1] == L'/' || (newpwd[1] == L'.' && (newpwd[2] == L'\0' || newpwd[2] == L'/')))) goto step6; /* step 5: search $CDPATH */ { char *mbsnewpwd = malloc_wcstombs(newpwd); if (mbsnewpwd == NULL) { wb_destroy(&curpath); xerror(EILSEQ, Ngt("unexpected error")); return Exit_ERROR; } char *const *cdpath = get_path_array(PA_CDPATH); char *path = which(mbsnewpwd, (cdpath != NULL) ? cdpath : (char *[]) { "", NULL }, is_directory); if (path != NULL) { if (strcmp(mbsnewpwd, path) != 0) printnewdir = true; wb_mbscat(&curpath, path); free(mbsnewpwd); free(path); goto step7; } free(mbsnewpwd); } step6: /* set the value of `curpath' */ assert(newpwd[0] != L'/'); assert(curpath.length == 0); wb_cat(&curpath, newpwd); step7: /* ensure the value of `curpath' is an absolute path */ if (!logical) goto step10; if (curpath.contents[0] != L'/') { wchar_t *oldcurpath = wb_towcs(&curpath); wb_init(&curpath); wb_cat(&curpath, origpwd); if (curpath.length == 0 || curpath.contents[curpath.length - 1] != L'/') wb_wccat(&curpath, L'/'); wb_catfree(&curpath, oldcurpath); } /* step 8: canonicalization */ assert(logical); { wchar_t *canon = canonicalize_path(curpath.contents); wb_destroy(&curpath); if (canon == NULL) { xerror(ENOTDIR, Ngt("`%ls'"), newpwd); return Exit_FAILURE; } wb_initwith(&curpath, canon); } /* step 9: determine `curpathoffset' */ assert(logical); /* If `origpwd' contains a character other than '/' and if `curpath' starts * with `origpwd', then a relative path to the new working directory can be * obtained by removing the matching prefix of `curpath'. */ if (origpwd[wcsspn(origpwd, L"/")] != L'\0') { wchar_t *s = matchwcsprefix(curpath.contents, origpwd); if (s != NULL && (s[-1] == L'/' || s[0] == L'/')) { if (s[0] == L'/') s++; assert(s[0] != L'/'); curpathoffset = s - curpath.contents; } } step10: /* do chdir */ assert(curpathoffset <= curpath.length); { char *mbscurpath = malloc_wcstombs(curpath.contents + curpathoffset); if (mbscurpath == NULL) { xerror(EILSEQ, Ngt("unexpected error")); wb_destroy(&curpath); return Exit_ERROR; } if (chdir(mbscurpath) < 0) { xerror(errno, Ngt("`%s'"), mbscurpath); free(mbscurpath); wb_destroy(&curpath); return Exit_FAILURE; } free(mbscurpath); } #ifndef NDEBUG newpwd = NULL; /* `newpwd' must not be used any more because it may be pointing to the * current value of $OLDPWD, which is going to be re-assigned to. */ #endif /* set $OLDPWD and $PWD */ if (origpwd != NULL) set_variable(L VAR_OLDPWD, xwcsdup(origpwd), SCOPE_GLOBAL, false); if (logical) { if (!posixly_correct) canonicalize_path_ex(&curpath); if (printnewdir) printf("%ls\n", curpath.contents); set_variable(L VAR_PWD, wb_towcs(&curpath), SCOPE_GLOBAL, false); } else { wb_destroy(&curpath); char *mbsnewpwd = xgetcwd(); if (mbsnewpwd != NULL) { if (printnewdir) printf("%s\n", mbsnewpwd); wchar_t *wnewpwd = realloc_mbstowcs(mbsnewpwd); if (wnewpwd != NULL) set_variable(L VAR_PWD, wnewpwd, SCOPE_GLOBAL, false); } } if (!posixly_correct) exec_variable_as_auxiliary_(VAR_YASH_AFTER_CD); return Exit_SUCCESS; } /* Canonicalizes a pathname. * * Dot components are removed. * * Dot-dot components are removed together with the preceding components. If * any of the preceding components is not a directory, it is an error. * * Redundant slashes are removed. * `path' must not be NULL. * The result is a newly malloced string if successful. NULL is returned on * error. */ wchar_t *canonicalize_path(const wchar_t *path) { wchar_t *const result = xmalloce(wcslen(path), 1, sizeof *result); wchar_t *rp = result; plist_T clist; pl_init(&clist); if (*path == L'/') { /* first slash */ path++; *rp++ = '/'; if (*path == L'/') { /* second slash */ path++; if (*path != L'/') /* third slash */ *rp++ = '/'; } } for (;;) { *rp = L'\0'; while (*path == L'/') path++; if (*path == L'\0') break; if (path[0] == L'.') { if (path[1] == L'\0') { /* ignore trailing dot component */ break; } else if (path[1] == L'/') { /* skip dot component */ path += 2; continue; } else if (path[1] == L'.' && (path[2] == L'\0' || path[2] == L'/') && clist.length > 0) { /* dot-dot component */ wchar_t *prev = clist.contents[clist.length - 1]; if (not_dotdot(prev)) { char *mbsresult = malloc_wcstombs(result); bool isdir = (mbsresult != NULL) && is_directory(mbsresult); free(mbsresult); if (isdir) { rp = prev; /* result[index] = L'\0'; */ pl_remove(&clist, clist.length - 1, 1); path += 2; continue; } else { /* error */ pl_destroy(&clist); free(result); return NULL; } } } } /* others */ pl_add(&clist, rp); if (clist.length > 1) *rp++ = L'/'; while (*path != L'\0' && *path != L'/') /* copy next component */ *rp++ = *path++; } pl_destroy(&clist); assert(*rp == L'\0'); return result; } bool not_dotdot(const wchar_t *p) { if (*p == L'/') p++; return wcscmp(p, L"..") != 0; } /* Removes "/.." components at the beginning of the string in the buffer * if the root directory and its parent are the same directory. */ void canonicalize_path_ex(xwcsbuf_T *buf) { if (starts_with_root_parent(buf->contents) && is_same_file("/", "/..")) { do wb_remove(buf, 0, 3); while (starts_with_root_parent(buf->contents)); if (buf->length == 0) wb_wccat(buf, L'/'); } } /* Returns true iff the given path starts with "/..". */ bool starts_with_root_parent(const wchar_t *path) { return path[0] == L'/' && path[1] == L'.' && path[2] == L'.' && (path[3] == L'\0' || path[3] == L'/'); } #if YASH_ENABLE_HELP const char cd_help[] = Ngt( "change the working directory" ); const char cd_syntax[] = Ngt( "\tcd [-L|-P] [directory]\n" ); #endif /* The "pwd" built-in, which accepts the following options: * -L: don't resolve symbolic links (default) * -P: resolve symbolic links * -L and -P are mutually exclusive: the one specified last is used. */ int pwd_builtin(int argc __attribute__((unused)), void **argv) { bool logical = true; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, pwd_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'L': logical = true; break; case L'P': logical = false; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (xoptind != argc) return too_many_operands_error(0); char *mbspwd; if (logical) { const wchar_t *pwd = getvar(L VAR_PWD); if (pwd != NULL && pwd[0] == L'/' && is_normalized_path(pwd)) { mbspwd = malloc_wcstombs(pwd); if (mbspwd != NULL) { if (is_same_file(mbspwd, ".")) goto print; free(mbspwd); } } } mbspwd = xgetcwd(); if (mbspwd == NULL) { xerror(errno, Ngt("cannot determine the current directory")); return Exit_FAILURE; } print: if (printf("%s\n", mbspwd) < 0) xerror(errno, Ngt("cannot print to the standard output")); free(mbspwd); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if YASH_ENABLE_HELP const char pwd_help[] = Ngt( "print the working directory" ); const char pwd_syntax[] = Ngt( "\tpwd [-L|-P]\n" ); #endif /* Options for the "hash" built-in. */ const struct xgetopt_T hash_options[] = { { L'a', L"all", OPTARG_NONE, false, NULL, }, { L'd', L"directory", OPTARG_NONE, false, NULL, }, { L'r', L"remove", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "hash" built-in, which accepts the following options: * -a: print all entries * -d: use the directory cache * -r: remove cache entries */ int hash_builtin(int argc, void **argv) { bool remove = false, all = false, dir = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, hash_options, 0)) != NULL) { switch (opt->shortopt) { case L'a': all = true; break; case L'd': dir = true; break; case L'r': remove = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (all && xoptind != argc) return too_many_operands_error(0); if (dir) { if (remove) { if (xoptind == argc) { // forget all clear_homedirhash(); } else { // forget the specified for (int i = xoptind; i < argc; i++) forget_home_directory(ARGV(i)); } } else { if (xoptind == argc) { // print all print_home_directories(); } else { // remember the specified for (int i = xoptind; i < argc; i++) if (!get_home_directory(ARGV(i), true)) xerror(0, Ngt("no such user `%ls'"), ARGV(i)); } } } else { if (remove) { if (xoptind == argc) { // forget all clear_cmdhash(); } else { // forget the specified for (int i = xoptind; i < argc; i++) { char *cmd = malloc_wcstombs(ARGV(i)); if (cmd != NULL) { forget_command_path(cmd); free(cmd); } } } } else { if (xoptind == argc) { // print all print_command_paths(all); } else { // remember the specified for (int i = xoptind; i < argc; i++) { if (wcschr(ARGV(i), L'/') != NULL) { xerror(0, Ngt("`%ls': a command name must not contain " "`/'"), ARGV(i)); continue; } char *cmd = malloc_wcstombs(ARGV(i)); if (cmd != NULL) { if (!get_command_path(cmd, true)) xerror(0, Ngt("command `%s' was not found " "in $PATH"), cmd); free(cmd); } } } } } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Prints the entries of the command hashtable. * Prints an error message to the standard error if failed to print to the * standard output. */ void print_command_paths(bool all) { kvpair_T kv; size_t index = 0; while ((kv = ht_next(&cmdhash, &index)).key != NULL) { const char *path = kv.value; if (path[0] != '/') continue; if (all || get_builtin(kv.key) == NULL) { if (!xprintf("%s\n", path)) { break; } } } } /* Prints the entries of the home directory hashtable. * Prints an error message to the standard error if failed to print to the * standard output. */ void print_home_directories(void) { kvpair_T kv; size_t index = 0; while ((kv = ht_next(&homedirhash, &index)).key != NULL) { const wchar_t *key = kv.key, *value = kv.value; if (!xprintf("~%ls=%ls\n", key, value)) { break; } } } #if YASH_ENABLE_HELP const char hash_help[] = Ngt( "remember, forget, or report command locations" ); const char hash_syntax[] = Ngt( "\thash command...\n" "\thash -r [command...]\n" "\thash [-a] # print remembered paths\n" "\thash -d user...\n" "\thash -d -r [user...]\n" "\thash -d # print remembered paths\n" ); #endif /* Options for the "umask" built-in. */ const struct xgetopt_T umask_options[] = { { L'S', L"symbolic", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "umask" built-in, which accepts the following option: * -S: symbolic output */ int umask_builtin(int argc, void **argv) { bool symbolic = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, umask_options, 0)) != NULL) { switch (opt->shortopt) { case L'S': symbolic = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } switch (argc - xoptind) { case 0: return print_umask(symbolic); case 1: return set_umask(ARGV(xoptind)); default: return too_many_operands_error(1); } } int print_umask(bool symbolic) { mode_t mode = umask(0); umask(mode); bool success; if (symbolic) success = print_umask_symbolic(mode); else success = print_umask_octal(mode); return success ? Exit_SUCCESS : Exit_FAILURE; } bool print_umask_octal(mode_t mode) { return xprintf("0%.3jo\n", (uintmax_t) mode); } bool print_umask_symbolic(mode_t mode) { xstrbuf_T outputtext; sb_init(&outputtext); sb_ccat(&outputtext, 'u'); sb_ccat(&outputtext, '='); if (!(mode & S_IRUSR)) sb_ccat(&outputtext, 'r'); if (!(mode & S_IWUSR)) sb_ccat(&outputtext, 'w'); if (!(mode & S_IXUSR)) sb_ccat(&outputtext, 'x'); sb_ccat(&outputtext, ','); sb_ccat(&outputtext, 'g'); sb_ccat(&outputtext, '='); if (!(mode & S_IRGRP)) sb_ccat(&outputtext, 'r'); if (!(mode & S_IWGRP)) sb_ccat(&outputtext, 'w'); if (!(mode & S_IXGRP)) sb_ccat(&outputtext, 'x'); sb_ccat(&outputtext, ','); sb_ccat(&outputtext, 'o'); sb_ccat(&outputtext, '='); if (!(mode & S_IROTH)) sb_ccat(&outputtext, 'r'); if (!(mode & S_IWOTH)) sb_ccat(&outputtext, 'w'); if (!(mode & S_IXOTH)) sb_ccat(&outputtext, 'x'); sb_ccat(&outputtext, '\n'); bool result = xprintf("%s", outputtext.contents); sb_destroy(&outputtext); return result; } int set_umask(const wchar_t *maskstr) { if (iswdigit(maskstr[0])) { /* parse as an octal number */ uintmax_t mask; wchar_t *end; errno = 0; mask = wcstoumax(maskstr, &end, 8); if (errno || *end != L'\0') { xerror(0, Ngt("`%ls' is not a valid mask specification"), maskstr); return Exit_ERROR; } umask((mode_t) (mask & (S_IRWXU | S_IRWXG | S_IRWXO))); return Exit_SUCCESS; } /* otherwise parse as a symbolic mode specification */ mode_t origmask = ~umask(0); mode_t newmask = origmask; const wchar_t *const savemaskstr = maskstr; for (;;) { mode_t who, perm; char op; /* '+', '-' or '=' */ /* parse 'who' */ who = 0; for (;; maskstr++) switch (*maskstr) { case L'u': who |= S_IRWXU; break; case L'g': who |= S_IRWXG; break; case L'o': who |= S_IRWXO; break; case L'a': who = S_IRWXU | S_IRWXG | S_IRWXO; break; default: goto who_end; } who_end: if (who == 0) who = S_IRWXU | S_IRWXG | S_IRWXO; /* parse 'op' */ op_start: op = *maskstr; switch (op) { case L'+': case L'-': case L'=': break; default: goto err; } maskstr++; /* parse 'perm' */ switch (*maskstr) { case L'u': perm = copy_user_mask(origmask); maskstr++; break; case L'g': perm = copy_group_mask(origmask); maskstr++; break; case L'o': perm = copy_other_mask(origmask); maskstr++; break; default: perm = 0; for (;; maskstr++) switch (*maskstr) { case L'r': perm |= S_IRUSR | S_IRGRP | S_IROTH; break; case L'w': perm |= S_IWUSR | S_IWGRP | S_IWOTH; break; case L'X': if (!(origmask & (S_IXUSR | S_IXGRP | S_IXOTH))) break; /* falls thru! */ case L'x': perm |= S_IXUSR | S_IXGRP | S_IXOTH; break; case L's': perm |= S_ISUID | S_ISGID; break; default: goto perm_end; } perm_end: break; } /* set newmask */ switch (op) { case L'+': newmask |= who & perm; break; case L'-': newmask &= ~(who & perm); break; case L'=': newmask = (~who & newmask) | (who & perm); break; default: assert(false); } switch (*maskstr) { case L'\0': goto parse_end; case L',': break; default: goto op_start; } maskstr++; } parse_end: umask(~newmask); return Exit_SUCCESS; err: umask(~origmask); xerror(0, Ngt("`%ls' is not a valid mask specification"), savemaskstr); return Exit_ERROR; } mode_t copy_user_mask(mode_t mode) { return ((mode & S_IRUSR) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0) | ((mode & S_IWUSR) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0) | ((mode & S_IXUSR) ? (S_IXUSR | S_IXGRP | S_IXOTH) : 0); } mode_t copy_group_mask(mode_t mode) { return ((mode & S_IRGRP) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0) | ((mode & S_IWGRP) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0) | ((mode & S_IXGRP) ? (S_IXUSR | S_IXGRP | S_IXOTH) : 0); } mode_t copy_other_mask(mode_t mode) { return ((mode & S_IROTH) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0) | ((mode & S_IWOTH) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0) | ((mode & S_IXOTH) ? (S_IXUSR | S_IXGRP | S_IXOTH) : 0); } #if YASH_ENABLE_HELP const char umask_help[] = Ngt( "print or set the file creation mask" ); const char umask_syntax[] = Ngt( "\tumask mode\n" "\tumask [-S]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/path.h000066400000000000000000000100101354143602500135140ustar00rootroot00000000000000/* Yash: yet another shell */ /* path.h: filename-related utilities */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_PATH_H #define YASH_PATH_H #include #include #include "xgetopt.h" struct stat; extern _Bool is_file(const char *path) __attribute__((nonnull)); extern _Bool is_regular_file(const char *path) __attribute__((nonnull)); extern _Bool is_irregular_file(const char *path) __attribute__((nonnull)); extern _Bool is_readable(const char *path) __attribute__((nonnull)); extern _Bool is_writable(const char *path) __attribute__((nonnull)); extern _Bool is_executable(const char *path) __attribute__((nonnull)); extern _Bool is_readable_regular(const char *path) __attribute__((nonnull)); extern _Bool is_executable_regular(const char *path) __attribute__((nonnull)); extern _Bool is_directory(const char *path) __attribute__((nonnull)); extern _Bool stat_result_same_file( const struct stat *stat1, const struct stat *stat2) __attribute__((nonnull,pure)); extern _Bool is_same_file(const char *path1, const char *path2) __attribute__((nonnull)); extern _Bool is_normalized_path(const wchar_t *path) __attribute__((nonnull)); extern char *xgetcwd(void) __attribute__((malloc,warn_unused_result)); extern char *which( const char *restrict name, char *const *restrict dirs, _Bool cond(const char *path)) __attribute__((nonnull(1),malloc,warn_unused_result)); extern int create_temporary_file( char **restrict filename, const char *restrict suffix, mode_t mode) __attribute__((nonnull)); /********** Command Hashtable **********/ extern void init_cmdhash(void); extern void clear_cmdhash(void); extern const char *get_command_path(const char *name, _Bool forcelookup) __attribute__((nonnull)); extern void fill_cmdhash(const char *prefix, _Bool ignorecase); extern const char *get_command_path_default(const char *name) __attribute__((nonnull)); /********** Home Directory Cache **********/ extern void init_homedirhash(void); extern const wchar_t *get_home_directory( const wchar_t *username, _Bool forcelookup) __attribute__((nonnull)); /********** wglob **********/ enum wglobflags_T { WGLB_MARK = 1 << 0, WGLB_CASEFOLD = 1 << 1, WGLB_PERIOD = 1 << 2, WGLB_NOSORT = 1 << 3, WGLB_RECDIR = 1 << 4, }; struct plist_T; extern _Bool wglob(const wchar_t *restrict pattern, enum wglobflags_T flags, struct plist_T *restrict list) __attribute__((nonnull)); /********** Built-ins **********/ extern int cd_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char cd_help[], cd_syntax[]; #endif extern int pwd_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char pwd_help[], pwd_syntax[]; #endif extern int hash_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char hash_help[], hash_syntax[]; #endif extern const struct xgetopt_T hash_options[]; extern int umask_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char umask_help[], umask_syntax[]; #endif extern const struct xgetopt_T umask_options[]; extern int change_directory( const wchar_t *newpwd, _Bool printnewdir, _Bool logical) __attribute__((nonnull,warn_unused_result)); #endif /* YASH_PATH_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/plist.c000066400000000000000000000127311354143602500137220ustar00rootroot00000000000000/* Yash: yet another shell */ /* plist.c: modifiable list of pointers */ /* (C) 2007-2015 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "plist.h" #include #include #include #include "util.h" /********** Utilities about Pointer Arrays **********/ /* Counts the number of the elements in the NULL-terminated pointer array. * The NULL element at the end of the array is not counted. */ size_t plcount(void *const *list) { size_t count = 0; while (list[count] != NULL) count++; return count; } /* Clones the specified NULL-terminated array of pointers. * Each pointer element is passed to function `copy' and the return value is * assigned to the new array element. * If the array contains more than `count' elements, only the first `count' * elements are copied. If the array elements are fewer than `count', the whole * array is copied. * If `array' is NULL, simply returns NULL. */ /* `xstrdup' and `copyaswcs' are suitable for `copy'. */ void **plndup(void *const *array, size_t count, void *copy(const void *p)) { if (array == NULL) return NULL; size_t realcount = 0; while (array[realcount] != NULL && realcount < count) realcount++; void **result = xmalloce(realcount, 1, sizeof *result); for (size_t i = 0; i < realcount; i++) result[i] = copy(array[i]); result[realcount] = NULL; return result; } /* Frees the NULL-terminated array of pointers and its elements. * `freer' is called for each array element and finally the array is `free'd. * If `ary' is NULL, this function does nothing. */ void plfree(void **ary, void freer(void *elem)) { if (ary != NULL) { for (void **a = ary; *a != NULL; a++) freer(*a); free(ary); } } /********** Pointer List **********/ /* A pointer list is a variable-length array of pointers to 'void'. * For any pointer list `list', it is assumed that `list.contents[list.length]' * is always NULL. * Besides, a pointer list may contain NULL or an pointer to itself * as an element of it. */ /* Many of the following pointer-list-related functions returns the list that * was given as the first argument. */ /* Initializes the pointer list as a new empty list with the specified initial * capacity. */ plist_T *pl_initwithmax(plist_T *list, size_t max) { list->contents = xmalloce(max, 1, sizeof (void *)); list->contents[0] = NULL; list->length = 0; list->maxlength = max; return list; } /* Changes the capacity of the specified list. * If `newmax' is less than the current length of the list, the end of * the pointer list is truncated. */ plist_T *pl_setmax(plist_T *list, size_t newmax) { list->contents = xrealloce(list->contents, newmax, 1, sizeof (void *)); list->maxlength = newmax; list->contents[newmax] = NULL; if (newmax < list->length) list->length = newmax; return list; } /* Increases the capacity of the list so that the capacity is no less than the * specified. */ plist_T *pl_ensuremax(plist_T *list, size_t max) { if (max <= list->maxlength) return list; size_t len15 = list->maxlength + (list->maxlength >> 1); if (max < len15) max = len15; if (max < list->maxlength + 6) max = list->maxlength + 6; return pl_setmax(list, max); } /* Clears the contents of the pointer list. * If `freer' is non-null, `freer' is called for each element in the list. */ plist_T *pl_clear(plist_T *list, void freer(void *elem)) { if (freer) for (size_t i = 0; i < list->length; i++) freer(list->contents[i]); list->contents[list->length = 0] = NULL; return list; } /* Replaces the contents of pointer list `list' with the elements of another * array `a'. * `ln' elements starting at offset `i' in `list' is removed and * the first `an' elements of `a' take place of them. * NULL elements are not treated specially. * If (list->length < i + ln), all the elements after offset `i' in the list is * replaced. Especially, if (list->length <= i), `a' is appended. * `a' must not be part of `list->contents'. */ plist_T *pl_replace( plist_T *restrict list, size_t i, size_t ln, void *const *restrict a, size_t an) { if (i > list->length) i = list->length; if (ln > list->length - i) ln = list->length - i; size_t newlength = add(list->length - ln, an); pl_ensuremax(list, newlength); memmove(list->contents + i + an, list->contents + i + ln, (list->length - (i + ln) + 1) * sizeof (void *)); memcpy(list->contents + i, a, an * sizeof (void *)); list->length = newlength; return list; } /* Appends `p' to the end of pointer list `list' as a new element. * `p' may be NULL or a pointer to the list itself. */ plist_T *pl_add(plist_T *list, const void *p) { pl_ensuremax(list, add(list->length, 1)); list->contents[list->length++] = (void *) p; list->contents[list->length] = NULL; return list; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/plist.h000066400000000000000000000141761354143602500137340ustar00rootroot00000000000000/* Yash: yet another shell */ /* plist.h: modifiable list of pointers */ /* (C) 2007-2010 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_PLIST_H #define YASH_PLIST_H #include #define Size_max ((size_t) -1) // = SIZE_MAX extern size_t plcount(void *const *list) __attribute__((pure,nonnull)); static inline void **pldup(void *const *array, void *copy(const void *p)) __attribute__((malloc,warn_unused_result,nonnull(2))); extern void **plndup( void *const *array, size_t count, void *copy(const void *p)) __attribute__((malloc,warn_unused_result,nonnull(3))); extern void plfree(void **ary, void freer(void *elem)); typedef struct plist_T { void **contents; size_t length, maxlength; } plist_T; static inline plist_T *pl_init(plist_T *list) __attribute__((nonnull)); static inline plist_T *pl_initwith(plist_T *list, void **array, size_t length) __attribute__((nonnull)); extern plist_T *pl_initwithmax(plist_T *list, size_t max) __attribute__((nonnull)); static inline void pl_destroy(plist_T *list) __attribute__((nonnull)); static inline void **pl_toary(plist_T *list) __attribute__((nonnull)); extern plist_T *pl_setmax(plist_T *list, size_t newmax) __attribute__((nonnull)); extern plist_T *pl_ensuremax(plist_T *list, size_t max) __attribute__((nonnull)); extern plist_T *pl_clear(plist_T *list, void freer(void *elem)) __attribute__((nonnull(1))); extern plist_T *pl_replace( plist_T *restrict list, size_t i, size_t ln, void *const *restrict a, size_t an) __attribute__((nonnull)); static inline plist_T *pl_ninsert( plist_T *restrict list, size_t i, void *const *restrict a, size_t n) __attribute__((nonnull)); static inline plist_T *pl_insert( plist_T *restrict list, size_t i, void *const *restrict a) __attribute__((nonnull)); static inline plist_T *pl_ncat( plist_T *restrict list, void *const *restrict a, size_t n) __attribute__((nonnull)); static inline plist_T *pl_cat( plist_T *restrict list, void *const *restrict a) __attribute__((nonnull)); static inline plist_T *pl_remove(plist_T *list, size_t i, size_t n) __attribute__((nonnull)); extern plist_T *pl_add(plist_T *list, const void *p) __attribute__((nonnull(1))); #ifndef PLIST_DEFAULT_MAX #define PLIST_DEFAULT_MAX 7 #endif /* Clones the specified NULL-terminated array of pointers. * Each pointer element is passed to function `copy' and the return value is * assigned to a new array element. * If `array' is NULL, simply returns NULL. */ /* `xstrdup' and `copyaswcs' are suitable for `copy'. */ void **pldup(void *const *array, void *copy(const void *p)) { return plndup(array, Size_max, copy); } /* Initializes the specified pointer list as a new empty list. */ plist_T *pl_init(plist_T *list) { return pl_initwithmax(list, PLIST_DEFAULT_MAX); } /* Initializes the specified pointer list with the specified array. * `array' must be a NULL-terminated array of pointers to void containing * exactly `length' elements and must be allocated by malloc. * `array' must not be modified or freed after the call to this function. */ plist_T *pl_initwith(plist_T *list, void **array, size_t length) { list->contents = array; list->length = list->maxlength = length; #ifdef assert assert(list->contents[list->length] == NULL); #endif return list; } /* Frees the specified pointer list. * Note that the list elements are not `free'd in this function. */ void pl_destroy(plist_T *list) { free(list->contents); } /* Frees the specified pointer list and returns the contents. * The caller must `free' the return value and its elements if needed. * If all the elements are pointers to byte strings, the return value can be * safely cast to (char **). */ void **pl_toary(plist_T *list) { return list->contents; } /* Inserts the first `n' elements of array `a' at offset `i' in pointer list * `list'. * NULL elements in `a' are not treated specially. * If (list->length <= i), the array elements are appended to the list. * Array `a' must not be part of `list->contents'. */ plist_T *pl_ninsert( plist_T *restrict list, size_t i, void *const *restrict a, size_t n) { return pl_replace(list, i, 0, a, n); } /* Inserts the elements of array `a' at offset `i' in pointer list `list'. * Array `a' must be terminated by a NULL element, which is not inserted. * If (list->length <= i), the array elements are appended to the list. * Array `a' must not be a part of `list->contents'. */ plist_T *pl_insert(plist_T *restrict list, size_t i, void *const *restrict a) { return pl_replace(list, i, 0, a, plcount(a)); } /* Appends the first `n' elements of array `a' to pointer list `list'. * NULL elements in `a' are not treated specially. * Array `a' must not be a part of `list->contents'. */ plist_T *pl_ncat(plist_T *restrict list, void *const *restrict a, size_t n) { return pl_replace(list, Size_max, 0, a, n); } /* Inserts the elements of array `a' to pointer list `list'. * Array `a' must be terminated by a NULL element, which is not inserted. * Array `a' must not be a part of `list->contents'. */ plist_T *pl_cat(plist_T *restrict list, void *const *restrict a) { return pl_replace(list, Size_max, 0, a, plcount(a)); } /* Removes the `n' elements at offset `i' in pointer list `list'. * It's the caller's responsibility to free the objects pointed by the removed * pointers. */ plist_T *pl_remove(plist_T *list, size_t i, size_t n) { return pl_replace(list, i, n, (void *[]) { NULL, }, 0); } #undef Size_max #endif /* YASH_PLIST_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/po/000077500000000000000000000000001354143602500130355ustar00rootroot00000000000000yash-2.49/po/Makefile.in000066400000000000000000000134701354143602500151070ustar00rootroot00000000000000# Makefile.in for PO files of yash # (C) 2007-2012 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # NOTE: In this Makefile it is assumed that the make implementation allows the # use of hyphens in target names. This means that there may be a strictly # POSIX-conforming implementation of make that rejects this Makefile. I have # never seen such an implementation but if you know of one please let me know. .POSIX: .SUFFIXES: .po .mo .ih @MAKE_SHELL@ topdir = .. subdir = po XGETTEXT = @XGETTEXT@ XGETTEXTFLAGS = @XGETTEXTFLAGS@ MSGINIT = @MSGINIT@ MSGFMT = @MSGFMT@ MSGFMTFLAGS = @MSGFMTFLAGS@ MSGMERGE = @MSGMERGE@ MSGMERGEFLAGS = @MSGMERGEFLAGS@ MSGCONV = @MSGCONV@ MSGFILTER = @MSGFILTER@ INSTALL = @INSTALL@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_DIR = @INSTALL_DIR@ POFILES_MAN = ja.po POFILES_QUOT = en@quot.po en@boldquot.po POFILES = $(POFILES_MAN) $(POFILES_QUOT) MOFILES = $(POFILES:.po=.mo) CATALOGS = @CATALOGS@ DOMAIN = @TARGET@ VERSION = @VERSION@ COPYRIGHT_HOLDER = Magicant DESTDIR = prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datarootdir = @datarootdir@ datadir = @datadir@ yashdatadir = $(datadir)/$(TARGET) localedir = @localedir@ mandir = @mandir@ docdir = @docdir@ htmldir = @htmldir@ # stamp-po is a timestamp denoting the last time at which the $(CATALOGS) have # been loosely updated. Its purpose is that when a developer or translator # checks out the package via VCS, "make" will update the $(DOMAIN).pot and the # $(CATALOGS), but subsequent invocations of "make" will do nothing. This # timestamp would not be necessary if updating the $(CATALOGS) would always # touch them; however, the rule for $(POFILES) has been designed to not touch # files that don't need to be changed. stamp-po: $(DOMAIN).pot @+[ -z "$(CATALOGS)" ] || $(MAKE) $(CATALOGS) touch $@ # This rule has no dependencies: we don't need to update $(DOMAIN).pot at # every "make" invocation, only create it when it is missing. # Only "make $(DOMAIN).pot-update", "make update-po" or "make dist" will force # an update. $(DOMAIN).pot: @+$(MAKE) $(DOMAIN).pot-update # This target rebuilds $(DOMAIN).pot; it is an expensive operation. $(DOMAIN).pot-update: (cd $(topdir) && find . -name '*.[ch]') | \ $(XGETTEXT) -d $(DOMAIN) -D $(topdir) -cTRANSLATORS: -f - -F --no-wrap \ --package-name=$(DOMAIN) --package-version=$(VERSION) \ --msgid-bugs-address=http://osdn.jp/projects/yash/forums/ \ --copyright-holder='$(COPYRIGHT_HOLDER)' $(XGETTEXTFLAGS) mv $(DOMAIN).po $(DOMAIN).pot # This target rebuilds a PO file if $(DOMAIN).pot has changed. # Note that a PO file is not touched if it doesn't need to be changed. $(POFILES_MAN): $(DOMAIN).pot $(MSGMERGE) -FU $(MSGMERGEFLAGS) $@ $(DOMAIN).pot # This target creates PO files that are automatically created from the # $(DOMAIN).pot file. This requires GNU sed (or a compatible one) that doesn't # automatically append a missing newline at the end of input. en@quot.po: $(DOMAIN).pot en@quot.ih en@quot.hd $(MSGINIT) -i $(DOMAIN).pot --no-translator -l en@quot -o - 2>/dev/null | \ sed -f en@quot.ih | \ $(MSGCONV) -t UTF-8 | \ $(MSGFILTER) sed -f quot.sed >$@ en@boldquot.po: $(DOMAIN).pot en@boldquot.ih en@boldquot.hd $(MSGINIT) -i $(DOMAIN).pot --no-translator -l en@boldquot -o - 2>/dev/null | \ sed -f en@boldquot.ih | \ $(MSGCONV) -t UTF-8 | \ $(MSGFILTER) sed -f boldquot.sed >$@ $(POFILES_QUOT:.po=.ih): insert-hd.sin target='$@'; lang=$${target%.*}; \ sed -e '/^#/d' -e "s/HEADER/$${lang}.hd/g" insert-hd.sin >$@ # This target updates all PO files as well as $(DOMAIN).pot. update-po: $(DOMAIN).pot-update @+$(MAKE) $(POFILES) .po.mo: $(MSGFMT) $(MSGFMTFLAGS) -o t-$@ $< mv -f t-$@ $@ install: install-data install-data: installdirs-data stamp-po @for lang in $(CATALOGS:.mo=); do \ dir=$(localedir)/$$lang/LC_MESSAGES; \ echo $(INSTALL_DATA) $$lang.mo $(DESTDIR)$$dir/$(DOMAIN).mo || true; \ $(INSTALL_DATA) $$lang.mo $(DESTDIR)$$dir/$(DOMAIN).mo; \ done installdirs: installdirs-data installdirs-data: @for lang in $(CATALOGS:.mo=); do \ dir=$(localedir)/$$lang/LC_MESSAGES; \ echo $(INSTALL_DIR) $(DESTDIR)$$dir || true; \ $(INSTALL_DIR) $(DESTDIR)$$dir; \ done uninstall: uninstall-data uninstall-data: @for lang in $(CATALOGS:.mo=); do \ dir=$(localedir)/$$lang/LC_MESSAGES; \ echo rm -f $(DESTDIR)$$dir/$(DOMAIN).mo || true; \ rm -f $(DESTDIR)$$dir/$(DOMAIN).mo; \ done DISTTARGETFILES = $(DOMAIN).pot $(POFILES) $(MOFILES) Makefile.in \ quot.sed boldquot.sed en@quot.ih en@quot.hd \ en@boldquot.ih en@boldquot.hd insert-hd.sin DISTFILES = $(DISTTARGETFILES) stamp-po distfiles: $(DISTTARGETFILES) touch stamp-po copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(TEST_SOURCES) $(topdir)/$(DISTTARGETDIR) mostlyclean: rm -fr $(DOMAIN).po clean: mostlyclean distclean: clean rm -fr Makefile maintainer-clean: distclean rm -fr $(DOMAIN).pot stamp-po $(POFILES_QUOT) $(POFILES_QUOT:.po=.ih) $(MOFILES) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) .PHONY: $(DOMAIN).pot-update update-po install install-data installdirs installdirs-data uninstall uninstall-data distfiles copy-distfiles mostlyclean clean distclean maintainer-clean yash-2.49/po/boldquot.sed000066400000000000000000000003311354143602500153600ustar00rootroot00000000000000s/"\([^"]*\)"/“\1”/g s/`\([^`']*\)'/‘\1’/g s/ '\([^`']*\)' / ‘\1’ /g s/ '\([^`']*\)'$/ ‘\1’/g s/^'\([^`']*\)' /‘\1’ /g s/“”/""/g s/“/“/g s/”/”/g s/‘/‘/g s/’/’/g yash-2.49/po/en@boldquot.hd000066400000000000000000000024711354143602500156320ustar00rootroot00000000000000# All this catalog "translates" are quotation characters. # The msgids must be ASCII and therefore cannot contain real quotation # characters, only substitutes like grave accent (0x60), apostrophe (0x27) # and double quote (0x22). These substitutes look strange; see # http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html # # This catalog translates grave accent (0x60) and apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019). # It also translates pairs of apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019) # and pairs of quotation mark (0x22) to # left double quotation mark (U+201C) and right double quotation mark (U+201D). # # When output to an UTF-8 terminal, the quotation characters appear perfectly. # When output to an ISO-8859-1 terminal, the single quotation marks are # transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to # grave/acute accent (by libiconv), and the double quotation marks are # transliterated to 0x22. # When output to an ASCII terminal, the single quotation marks are # transliterated to apostrophes, and the double quotation marks are # transliterated to 0x22. # # This catalog furthermore displays the text between the quotation marks in # bold face, assuming the VT100/XTerm escape sequences. # yash-2.49/po/en@quot.hd000066400000000000000000000022631354143602500147700ustar00rootroot00000000000000# All this catalog "translates" are quotation characters. # The msgids must be ASCII and therefore cannot contain real quotation # characters, only substitutes like grave accent (0x60), apostrophe (0x27) # and double quote (0x22). These substitutes look strange; see # http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html # # This catalog translates grave accent (0x60) and apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019). # It also translates pairs of apostrophe (0x27) to # left single quotation mark (U+2018) and right single quotation mark (U+2019) # and pairs of quotation mark (0x22) to # left double quotation mark (U+201C) and right double quotation mark (U+201D). # # When output to an UTF-8 terminal, the quotation characters appear perfectly. # When output to an ISO-8859-1 terminal, the single quotation marks are # transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to # grave/acute accent (by libiconv), and the double quotation marks are # transliterated to 0x22. # When output to an ASCII terminal, the single quotation marks are # transliterated to apostrophes, and the double quotation marks are # transliterated to 0x22. # yash-2.49/po/insert-hd.sin000066400000000000000000000012401354143602500154420ustar00rootroot00000000000000# Sed script that inserts the file called HEADER before the header entry. # # At each occurrence of a line starting with "msgid ", we execute the following # commands. At the first occurrence, insert the file. At the following # occurrences, do nothing. The distinction between the first and the following # occurrences is achieved by looking at the hold space. /^msgid /{ x # Test if the hold space is empty. s/m/m/ ta # Yes it was empty. First occurrence. Read the file. r HEADER # Output the file's contents by reading the next line. But don't lose the # current line while doing this. g N bb :a # The hold space was nonempty. Following occurrences. Do nothing. x :b } yash-2.49/po/ja.po000066400000000000000000001434441354143602500140010ustar00rootroot00000000000000# Japanese translations for yash package # yash パッケージのメッセージの和訳 # Copyright (C) 2010-2019 Magicant # This file is distributed under the same license as the yash package. # WATANABE Yuki , 2010-2019. # msgid "" msgstr "" "Project-Id-Version: yash 2.49\n" "Report-Msgid-Bugs-To: http://osdn.jp/projects/yash/forums/\n" "POT-Creation-Date: 2019-09-21 20:49+0900\n" "PO-Revision-Date: 2018-12-16 16:45+0900\n" "Last-Translator: WATANABE Yuki \n" "Language-Team: Japanese\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: alias.c:403 #, c-format msgid "%ls: an alias for `%ls'\n" msgstr "%ls: 「%ls」へのエイリアス\n" #: alias.c:499 alias.c:550 #, c-format msgid "no such alias `%ls'" msgstr "「%ls」というエイリアスはありません" #: alias.c:502 #, c-format msgid "`%ls' is not a valid alias name" msgstr "「%ls」は有効なエイリアス名ではありません" #: alias.c:511 msgid "define or print aliases" msgstr "エイリアスを定義または表示する" #: alias.c:514 msgid "\talias [-gp] [name[=value]...]\n" msgstr "\talias [-gp] [名前[=値]...]\n" #: alias.c:558 msgid "undefine aliases" msgstr "エイリアスの定義を削除する" #: alias.c:561 msgid "" "\tunalias name...\n" "\tunalias -a\n" msgstr "" "\tunalias 名前...\n" "\tunalias -a\n" #: arith.c:171 arith.c:213 msgid "arithmetic: invalid syntax" msgstr "数式展開: 構文が正しくありません" #: arith.c:208 msgid "the index is not an integer" msgstr "インデックスが整数ではありません" #. TRANSLATORS: This error message is shown when the target #. * of an assignment is not a variable. #: arith.c:264 msgid "arithmetic: cannot assign to a number" msgstr "数式展開: 代入先は変数でなければなりません" #: arith.c:310 arith.c:1021 #, c-format msgid "arithmetic: parameter `%ls' is not set" msgstr "数式展開: パラメータ「%ls」は存在しません" #: arith.c:499 arith.c:947 #, c-format msgid "arithmetic: `%ls' is missing" msgstr "数式展開: 「%ls」が抜けています" #: arith.c:810 arith.c:886 #, c-format msgid "arithmetic: operator `%ls' is not supported" msgstr "数式展開: 演算子「%ls」は使えません" #. TRANSLATORS: This error message is shown when the operand of #. * the "++" or "--" operator is not a variable. #: arith.c:823 arith.c:900 #, c-format msgid "arithmetic: operator `%ls' requires a variable" msgstr "数式展開: 演算子「%ls」の対象は変数でなければなりません" #: arith.c:962 msgid "arithmetic: a value is missing" msgstr "数式展開: 値が抜けています" #: arith.c:999 arith.c:1050 #, c-format msgid "arithmetic: `%ls' is not a valid number" msgstr "数式展開:「%ls」は有効な数値ではありません" #: arith.c:1320 #, c-format msgid "arithmetic: `%lc' is not a valid number or operator" msgstr "数式展開:「%lc」は有効な数値や演算子ではありません" #: arith.c:1363 msgid "arithmetic: division by zero" msgstr "数式展開: 0 による除算" #: builtin.c:249 #, c-format msgid "the -%lc option cannot be used with the -%lc option" msgstr "-%lc オプションは -%lc オプションとは一緒に使えません" #: builtin.c:275 #, c-format msgid "this command requires an operand" msgid_plural "this command requires %zu operands" msgstr[0] "オペランドが足りません (%zu 個必要)" #. TRANSLATORS: This message is printed when a command that takes no #. * operand was invoked with some operands. #: builtin.c:288 msgid "no operand is expected" msgstr "このコマンドはオペランドを受け付けません" #. TRANSLATORS: This message is printed when a command was invoked with #. * the wrong number of operands. #: builtin.c:292 msgid "too many operands are specified" msgstr "オペランドの数が多すぎます" #: builtin.c:344 yash.c:391 msgid "Try `man yash' for details.\n" msgstr "詳しくは: man yash\n" #: builtin.c:359 #, c-format msgid "no such built-in `%ls'" msgstr "「%ls」というような組込みコマンドはありません" #. TRANSLATORS: This is printed before syntax info of a built-in. #: builtin.c:367 #, c-format msgid "" "Syntax:\n" "%s\n" msgstr "" "構文:\n" "%s\n" #. TRANSLATORS: This text is printed before a list of options. #: builtin.c:386 builtin.c:406 msgid "Options:\n" msgstr "オプション:\n" #: builtin.c:545 msgid "do nothing" msgstr "何もしない" #: builtin.c:548 msgid "\t: [...]\n" msgstr "\t: [...]\n" #: builtin.c:552 msgid "do nothing successfully" msgstr "何もせず成功を返す" #: builtin.c:555 msgid "\ttrue\n" msgstr "\ttrue\n" #: builtin.c:559 msgid "do nothing unsuccessfully" msgstr "何もせず失敗を返す" #: builtin.c:562 msgid "\tfalse\n" msgstr "\tfalse\n" #: builtin.c:587 msgid "print usage of built-in commands" msgstr "組込みコマンドの使い方を表示する" #: builtin.c:590 msgid "\thelp [built-in...]\n" msgstr "\thelp [組込みコマンド...]\n" #: builtins/printf.c:206 builtins/printf.c:333 history.c:1458 job.c:1107 #: path.c:1379 util.c:285 msgid "cannot print to the standard output" msgstr "標準出力に出力できません" #: builtins/printf.c:268 msgid "print arguments" msgstr "引数を出力する" #: builtins/printf.c:271 msgid "\techo [string...]\n" msgstr "\techo [文字列...]\n" #: builtins/printf.c:412 msgid "cannot parse the format" msgstr "書式を解析できません" #: builtins/printf.c:521 msgid "the conversion specifier is missing" msgstr "変換指定子が抜けています" #: builtins/printf.c:525 #, c-format msgid "`%lc' is not a valid conversion specifier" msgstr "「%lc」は有効な変換指定子ではありません" #: builtins/printf.c:530 #, c-format msgid "invalid flag for conversion specifier `%lc'" msgstr "変換指定子「%lc」に対するフラグが不正です" #: builtins/printf.c:660 #, c-format msgid "`%ls' is not a valid number" msgstr "「%ls」は有効な数値ではありません" #: builtins/printf.c:694 builtins/test.c:573 builtins/test.c:579 #: builtins/ulimit.c:229 exec.c:1754 exec.c:1827 history.c:1714 sig.c:1390 #: variable.c:2117 variable.c:2177 variable.c:2216 variable.c:2400 yash.c:631 #, c-format msgid "`%ls' is not a valid integer" msgstr "「%ls」は有効な整数ではありません" #: builtins/printf.c:759 msgid "print a formatted string" msgstr "文字列を整形して出力する" #: builtins/printf.c:762 msgid "\tprintf format [value...]\n" msgstr "\tprintf 書式 [値...]\n" #: builtins/test.c:99 builtins/test.c:442 parser.c:917 parser.c:1414 #: parser.c:1502 parser.c:1527 parser.c:1659 parser.c:1698 parser.c:2207 #: parser.c:2552 parser.c:2640 parser.c:2720 parser.c:2761 parser.c:2840 #, c-format msgid "`%ls' is missing" msgstr "「%ls」が抜けています" #: builtins/test.c:130 #, c-format msgid "`%ls' is not a valid operator" msgstr "「%ls」は有効な演算子ではありません" #: builtins/test.c:155 #, c-format msgid "`%ls' is not a unary operator" msgstr "「%ls」は単項演算子ではありません" #: builtins/test.c:176 builtins/test.c:644 builtins/test.c:652 exec.c:1963 #: exec.c:2107 exec.c:2116 history.c:1492 lineedit/keymap.c:476 path.c:1106 #: path.c:1173 strbuf.c:573 strbuf.c:596 msgid "unexpected error" msgstr "想定外のエラーです" #: builtins/test.c:381 #, c-format msgid "`%ls' is not a binary operator" msgstr "「%ls」は二項演算子ではありません" #: builtins/test.c:433 #, c-format msgid "an expression is missing after `%ls'" msgstr "「%ls」の後で式が抜けています" #: builtins/test.c:699 msgid "evaluate a conditional expression" msgstr "条件式を評価する" #: builtins/test.c:702 msgid "" "\ttest expression\n" "\t[ expression ]\n" msgstr "" "\ttest 式\n" "\t[ 式 ]\n" #: builtins/ulimit.c:65 msgid "file size (blocks)" msgstr "ファイルサイズ (ブロック数)    " #: builtins/ulimit.c:73 msgid "core file size (blocks)" msgstr "コアファイルサイズ (ブロック数)  " #: builtins/ulimit.c:75 msgid "data segment size (kbytes)" msgstr "データセグメントサイズ (キロバイト)" #: builtins/ulimit.c:78 msgid "max nice" msgstr "最大 nice             " #: builtins/ulimit.c:83 msgid "pending signals" msgstr "処理待ちシグナル         " #: builtins/ulimit.c:87 msgid "locked memory (kbytes)" msgstr "ロックしたメモリ (キロバイト)   " #: builtins/ulimit.c:91 msgid "resident set size (kbytes)" msgstr "レジデントセットサイズ (キロバイト)" #: builtins/ulimit.c:94 msgid "open files" msgstr "開けるファイル数         " #: builtins/ulimit.c:97 msgid "message queue size (bytes)" msgstr "メッセージキューサイズ (バイト)  " #: builtins/ulimit.c:101 msgid "real-time priority" msgstr "リアルタイム優先度        " #: builtins/ulimit.c:104 msgid "stack size (kbytes)" msgstr "スタックサイズ (キロバイト)    " #: builtins/ulimit.c:106 msgid "CPU time (seconds)" msgstr "CPU 時間 (秒)           " #: builtins/ulimit.c:109 msgid "user processes" msgstr "ユーザプロセス数         " #: builtins/ulimit.c:113 msgid "memory (kbytes)" msgstr "メモリ (キロバイト)        " #: builtins/ulimit.c:117 msgid "file locks" msgstr "ファイルのロック         " #: builtins/ulimit.c:170 builtins/ulimit.c:245 #, c-format msgid "cannot get the current limit for the resource type of `%s'" msgstr "リソース「%s」の現在の制限値を取得できません" #: builtins/ulimit.c:217 msgid "the soft limit cannot exceed the hard limit" msgstr "ソフトリミットはハードリミットより大きくできません" #: builtins/ulimit.c:222 msgid "failed to set the limit" msgstr "リミットの設定に失敗しました" #: builtins/ulimit.c:251 #, c-format msgid "-%lc: %-30s " msgstr "-%lc: %-52s " #: builtins/ulimit.c:263 msgid "unlimited" msgstr "無制限" #: builtins/ulimit.c:270 msgid "set or print a resource limitation" msgstr "リソースの制限を設定または表示する" #: builtins/ulimit.c:273 msgid "" "\tulimit -a [-H|-S]\n" "\tulimit [-H|-S] [-efilnqrstuvx] [limit]\n" msgstr "" "\tulimit -a [-H|-S]\n" "\tulimit [-H|-S] [-efilnqrstuvx] [制限]\n" #: exec.c:780 msgid "cannot open a pipe" msgstr "パイプを開けません" #: exec.c:1012 msgid "cannot make a child process" msgstr "子プロセスを生成できません" #: exec.c:1237 exec.c:2131 exec.c:2358 #, c-format msgid "no such command `%s'" msgstr "「%s」というようなコマンドはありません" #: exec.c:1291 #, c-format msgid "cannot execute command `%s'" msgstr "コマンド「%s」を実行できません" #: exec.c:1292 #, c-format msgid "cannot execute command `%s' (%s)" msgstr "コマンド「%s」(%s) を実行できません" #: exec.c:1395 #, c-format msgid "cannot invoke a new shell to execute script `%s'" msgstr "スクリプト「%s」を実行するための新しいシェルを起動できません" #: exec.c:1454 exec.c:1476 msgid "cannot open a pipe for the command substitution" msgstr "コマンド置換のためのパイプを開けません" #: exec.c:1513 msgid "command substitution" msgstr "コマンド置換" #: exec.c:1764 msgid "cannot be used in the interactive mode" msgstr "対話モードでは使えません" #: exec.c:1774 msgid "return from a function or script" msgstr "関数やスクリプトから抜ける" #: exec.c:1777 msgid "\treturn [-n] [exit_status]\n" msgstr "\treturn [-n] [終了ステータス]\n" #: exec.c:1809 msgid "not in an iteration" msgstr "反復実行の途中ではありません" #: exec.c:1830 #, c-format msgid "%u is not a positive integer" msgstr "%u は正の整数ではありません" #: exec.c:1840 msgid "not in a loop" msgstr "ループの途中ではありません" #: exec.c:1859 msgid "exit a loop" msgstr "ループを抜ける" #: exec.c:1862 msgid "" "\tbreak [count]\n" "\tbreak -i\n" msgstr "" "\tbreak [深さ]\n" "\tbreak -i\n" #: exec.c:1867 msgid "continue a loop" msgstr "ループの先頭に戻る" #: exec.c:1870 msgid "" "\tcontinue [count]\n" "\tcontinue -i\n" msgstr "" "\tcontinue [深さ]\n" "\tcontinue -i\n" #: exec.c:1910 msgid "evaluate arguments as a command" msgstr "引数をコマンドとして実行する" #: exec.c:1913 msgid "\teval [-i] [argument...]\n" msgstr "\teval [-i] [引数...]\n" #: exec.c:1972 #, c-format msgid "file `%s' was not found in $YASH_LOADPATH" msgstr "ファイル「%s」は $YASH_LOADPATH 内に見つかりませんでした" #: exec.c:1982 #, c-format msgid "file `%s' was not found in $PATH" msgstr "ファイル「%s」は $PATH 内に見つかりませんでした" #: exec.c:1994 redir.c:279 yash.c:206 #, c-format msgid "cannot open file `%s'" msgstr "ファイル「%s」を開けません" #: exec.c:2033 msgid "read a file and execute commands" msgstr "ファイルをスクリプトとして実行する" #: exec.c:2036 msgid "\t. [-AL] file [argument...]\n" msgstr "\t. [-AL] ファイル [引数...]\n" #: exec.c:2084 yash.c:618 #, c-format msgid "You have a stopped job!" msgid_plural "You have %zu stopped jobs!" msgstr[0] "停止中のジョブが %zu 個あります!" #: exec.c:2088 msgid " Use the -f option to exec anyway.\n" msgstr " それでも exec するには -f オプションを付けてください。\n" #: exec.c:2178 msgid "replace the shell process with an external command" msgstr "シェルのプロセスを外部コマンドに変える" #: exec.c:2181 msgid "\texec [-cf] [-a name] [command [argument...]]\n" msgstr "\texec [-cf] [-a 名前] [コマンド [引数...]]\n" #: exec.c:2241 msgid "the -a or -k option must be used with the -v option" msgstr "-a および -k オプションは -v オプションと一緒にしか使えません" #: exec.c:2335 #, c-format msgid "%ls: a shell keyword\n" msgstr "%ls: シェルの予約語\n" #: exec.c:2364 #, c-format msgid "%s: a special built-in\n" msgstr "%s: 特殊組込みコマンド\n" #: exec.c:2368 #, c-format msgid "%s: a semi-special built-in\n" msgstr "%s: 準特殊組込みコマンド\n" #: exec.c:2380 #, c-format msgid "%s: a regular built-in (not found in $PATH)\n" msgstr "%s: 通常の組込みコマンド ($PATH 内に存在せず)\n" #: exec.c:2381 #, c-format msgid "%s: a regular built-in at %s\n" msgstr "%s: 通常の組込みコマンド (%s)\n" #: exec.c:2388 #, c-format msgid "%s: a function\n" msgstr "%s: 関数\n" #: exec.c:2404 #, c-format msgid "%s: an external command at %s\n" msgstr "%s: 外部コマンド (%s)\n" #: exec.c:2426 #, c-format msgid "%s: an external command at %s/%s\n" msgstr "%s: 外部コマンド (%s/%s)\n" #: exec.c:2435 msgid "execute or identify a command" msgstr "コマンドを実行または特定する" #: exec.c:2438 msgid "" "\tcommand [-befp] command [argument...]\n" "\tcommand -v|-V [-abefkp] command...\n" msgstr "" "\tcommand [-befp] コマンド [引数...]\n" "\tcommand -v|-V [-abefkp] コマンド...\n" #: exec.c:2443 msgid "identify a command" msgstr "コマンドを特定する" #: exec.c:2446 msgid "\ttype command...\n" msgstr "\ttype コマンド...\n" #: exec.c:2484 msgid "cannot get the time data" msgstr "時間情報を取得できません" #: exec.c:2501 msgid "print CPU time usage" msgstr "消費 CPU 時間を表示する" #: exec.c:2504 msgid "\ttimes\n" msgstr "\ttimes\n" #: expand.c:349 expand.c:355 expand.c:373 redir.c:457 msgid "redirection" msgstr "リダイレクト" #: expand.c:363 #, c-format msgid "filename `%ls' matches more than one file" msgstr "ファイル名「%ls」に当てはまるファイルが複数あります" #: expand.c:660 msgid "the parameter index is invalid" msgstr "パラメータのインデックスが正しくありません" #: expand.c:816 msgid "a nested parameter expansion cannot be assigned" msgstr "入れ子のパラメータ展開は代入できません" #: expand.c:819 #, c-format msgid "cannot assign to parameter `%ls' in parameter expansion" msgstr "パラメータ展開ではパラメータ「%ls」に代入することはできません" #: expand.c:825 #, c-format msgid "" "the specified index does not support assignment in the parameter expansion " "of array `%ls'" msgstr "" "配列「%ls」のパラメータ展開において指定されているインデックスは代入できません" #: expand.c:865 expand.c:1046 #, c-format msgid "parameter `%ls' is not set" msgstr "パラメータ「%ls」は存在しません" #: expand.c:1042 msgid "the parameter value is empty" msgstr "パラメータの値が空です" #: expand.c:1045 #, c-format msgid "parameter `%ls' is not set or has an empty value" msgstr "パラメータ「%ls」は存在しないか値が空です" #: history.c:1274 msgid "the -n or -v option must be used with the -l option" msgstr "-n および -v オプションは -l オプションと一緒にしか使えません" #: history.c:1280 history.c:1659 msgid "cannot be used during line-editing" msgstr "行編集中には使えません" #: history.c:1296 msgid "the command history is empty" msgstr "コマンド履歴は空です" #: history.c:1319 history.c:1774 #, c-format msgid "no such history entry `%ls'" msgstr "「%ls」というような履歴項目はありません" #: history.c:1421 #, c-format msgid "no such history entry beginning with `%ls'" msgstr "「%ls」で始まるような履歴項目はありません" #: history.c:1529 lineedit/editing.c:2893 msgid "cannot create a temporary file to edit history" msgstr "履歴を編集するための一時ファイルを作成できません" #: history.c:1534 lineedit/editing.c:2898 #, c-format msgid "cannot open temporary file `%s'" msgstr "一時ファイル「%s」が開けません" #: history.c:1542 lineedit/editing.c:2906 msgid "cannot invoke the editor to edit history" msgstr "履歴を編集するためのエディタを起動できません" #: history.c:1545 history.c:1571 lineedit/editing.c:2909 redir.c:826 #, c-format msgid "failed to remove temporary file `%s'" msgstr "一時ファイル「%s」を削除できませんでした" #: history.c:1563 msgid "the editor returned a non-zero exit status" msgstr "エディタが 0 以外の終了ステータスを返しました" #: history.c:1568 #, c-format msgid "cannot read commands from file `%s'" msgstr "ファイル「%s」からコマンドを読み込めません" #: history.c:1626 msgid "list or re-execute command history" msgstr "コマンド履歴を表示したりコマンドを再実行したりする" #: history.c:1629 msgid "" "\tfc [-qr] [-e editor] [first [last]]\n" "\tfc -s [-q] [old=new] [first]\n" "\tfc -l [-nrv] [first [last]]\n" msgstr "" "\tfc [-qr] [-e エディタ] [始点 [終点]]\n" "\tfc -s [-q] [元=先] [始点]\n" "\tfc -l [-nrv] [始点 [終点]]\n" #: history.c:1821 #, c-format msgid "cannot read history from file `%ls'" msgstr "ファイル「%ls」から履歴を読み込めません" #: history.c:1855 #, c-format msgid "cannot write history to file `%ls'" msgstr "ファイル「%ls」に履歴を書き込めません" #: history.c:1876 msgid "manage command history" msgstr "コマンド履歴を管理する" #: history.c:1879 msgid "\thistory [-cF] [-d entry] [-s command] [-r file] [-w file] [count]\n" msgstr "" "\thistory [-cF] [-d 項目] [-s コマンド] [-r ファイル] [-w ファイル] [個数]\n" #: input.c:170 lineedit/lineedit.c:221 msgid "cannot read input" msgstr "入力を読み込めません" #: input.c:288 msgid "prompt" msgstr "プロンプト" #: job.c:538 job.c:1059 job.c:1165 job.c:1345 job.c:1438 #, c-format msgid "job specification `%ls' is ambiguous" msgstr "ジョブ指定「%ls」は曖昧です" #: job.c:543 job.c:1062 job.c:1170 job.c:1441 #, c-format msgid "no such job `%ls'" msgstr "「%ls」に該当するジョブはありません" #: job.c:546 job.c:1172 #, c-format msgid "`%ls' is not a job-controlled job" msgstr "「%ls」はジョブ制御の対象となっているジョブではありません" #: job.c:688 job.c:731 msgid "Running" msgstr "実行中" #: job.c:691 #, c-format msgid "Stopped(SIG%ls)" msgstr "停止中(SIG%ls)" #: job.c:702 msgid "Done" msgstr "完了 " #: job.c:705 #, c-format msgid "Done(%d)" msgstr "完了(%d) " #: job.c:713 #, c-format msgid "Killed (SIG%ls: core dumped)" msgstr "中止 (SIG%ls: コアダンプ)" #: job.c:716 #, c-format msgid "Killed (SIG%ls)" msgstr "中止 (SIG%ls)" #. TRANSLATORS: the translated format string can be different #. * from the original only in the number of spaces. This is required #. * for POSIX compliance. #: job.c:789 #, c-format msgid "[%zu] %c %-20s %ls\n" msgstr "[%zu] %c %-25s %ls\n" #. TRANSLATORS: the translated format string can be different #. * from the original only in the number of spaces. This is required #. * for POSIX compliance. #: job.c:808 #, c-format msgid "[%zu] %c %5jd %-20s %ls\n" msgstr "[%zu] %c %5jd %-25s %ls\n" #. TRANSLATORS: the translated format string can be different #. * from the original only in the number of spaces. This is required #. * for POSIX compliance. #: job.c:822 #, c-format msgid " %5jd %-20s | %ls\n" msgstr " %5jd %-25s | %ls\n" #: job.c:872 #, c-format msgid "The process was killed by SIG%ls: %s\n" msgstr "プロセスは SIG%ls により強制終了しました (%s)\n" #: job.c:875 #, c-format msgid "The process was killed by SIG%ls\n" msgstr "プロセスは SIG%ls により強制終了しました\n" #: job.c:1052 job.c:1158 job.c:1339 job.c:1432 #, c-format msgid "`%ls' is not a valid job specification" msgstr "「%ls」は有効なジョブ ID ではありません" #: job.c:1116 msgid "print info about jobs" msgstr "ジョブの情報を表示する" #: job.c:1119 msgid "\tjobs [-lnprs] [job...]\n" msgstr "\tjobs [-lnprs] [ジョブ...]\n" #: job.c:1145 msgid "job control is disabled" msgstr "ジョブコントロールは無効です" #: job.c:1181 job.c:1448 msgid "there is no current job" msgstr "現在のジョブはありません" #: job.c:1183 msgid "the current job is not a job-controlled job" msgstr "現在のジョブはジョブ制御の対象となっているジョブではありません" #: job.c:1222 #, c-format msgid "job %%%zu has already terminated" msgstr "ジョブ %%%zu は既に終了しています" #: job.c:1268 msgid "run jobs in the foreground" msgstr "ジョブをフォアグラウンドで実行する" #: job.c:1271 msgid "\tfg [job...]\n" msgstr "\tfg [ジョブ...]\n" #: job.c:1275 msgid "run jobs in the background" msgstr "ジョブをバックグラウンドで実行する" #: job.c:1278 msgid "\tbg [job...]\n" msgstr "\tbg [ジョブ...]\n" #: job.c:1395 msgid "wait for jobs to terminate" msgstr "ジョブの終了を待つ" #: job.c:1398 msgid "\twait [job or process_id...]\n" msgstr "\twait [ジョブまたはプロセス番号...]\n" #: job.c:1458 msgid "disown jobs" msgstr "ジョブを放棄する" #: job.c:1461 msgid "" "\tdisown [job...]\n" "\tdisown -a\n" msgstr "" "\tdisown [ジョブ...]\n" "\tdisown -a\n" #: lineedit/complete.c:1512 #, c-format msgid "more than one -%lc option is specified" msgstr "-%lc が二回以上指定されています" #: lineedit/complete.c:1524 msgid "the complete built-in can be used during command line completion only" msgstr "complete 組込みコマンドはコマンドライン補完の最中にしか使えません" #: lineedit/complete.c:1537 #, c-format msgid "the specified prefix `%ls' does not match the target word `%ls'" msgstr "指定された接頭辞「%ls」は補完対象「%ls」に適合しません" #: lineedit/complete.c:1585 msgid "generate completion candidates" msgstr "補完候補を生成する" #: lineedit/complete.c:1588 msgid "" "\tcomplete [-A pattern] [-R pattern] [-T] [-P prefix] [-S suffix] \\\n" "\t [-abcdfghjkuv] [[-O] [-D description] words...]\n" msgstr "" "\tcomplete [-A パターン] [-R パターン] [-T] [-P 接頭辞] [-S 接尾辞] \\\n" "\t [-abcdfghjkuv] [[-O] [-D 説明] 単語...]\n" #: lineedit/display.c:1366 #, c-format msgid "Candidate %zu of %zu; Page %zu of %zu" msgstr "候補 %zu/%zu、ページ %zu/%zu" #: lineedit/display.c:1382 msgid "No candidates" msgstr "候補なし" #: lineedit/editing.c:2963 msgid "lineedit" msgstr "行編集" #: lineedit/keymap.c:422 msgid "option combination is invalid" msgstr "オプションの組み合わせが正しくありません" #: lineedit/keymap.c:429 msgid "no option is specified" msgstr "オプションが指定されていません" #: lineedit/keymap.c:463 msgid "cannot bind an empty key sequence" msgstr "空のキーシーケンスにコマンドを割り当てることはできません" #: lineedit/keymap.c:487 #, c-format msgid "no such editing command `%ls'" msgstr "「%ls」というような編集コマンドはありません" #: lineedit/keymap.c:521 #, c-format msgid "key sequence `%ls' is not bound" msgstr "キーシーケンス「%ls」にコマンドは割り当てられていません" #: lineedit/keymap.c:567 msgid "set or print key bindings for line-editing" msgstr "行編集のキー割り当てを設定または表示する" #: lineedit/keymap.c:570 msgid "" "\tbindkey -aev [key_sequence [command]]\n" "\tbindkey -l\n" msgstr "" "\tbindkey -aev [キーシーケンス [コマンド]]\n" "\tbindkey -l\n" #: mail.c:164 mail.c:237 msgid "You have new mail." msgstr "新着メールがあります。" #: option.c:423 xgetopt.c:414 #, c-format msgid "the -%lc option requires an argument" msgstr "-%lc オプションの引数が抜けています" #: option.c:470 option.c:589 sig.c:1334 xgetopt.c:382 #, c-format msgid "`%ls' is not a valid option" msgstr "「%ls」は有効なオプションではありません" #: option.c:612 xgetopt.c:417 #, c-format msgid "the --%ls option requires an argument" msgstr "--%ls オプションは引数を取りません" #: option.c:625 xgetopt.c:431 #, c-format msgid "%ls: the --%ls option does not take an argument" msgstr "%ls: --%ls オプションは引数を取りません" #: option.c:634 xgetopt.c:392 #, c-format msgid "option `%ls' is ambiguous" msgstr "オプション「%ls」は曖昧です" #: option.c:658 #, c-format msgid "the %ls option cannot be changed once the shell has been initialized" msgstr "シェルが初期化された後は %ls オプションを変更することはできません" #: option.c:924 msgid "on" msgstr "入" #: option.c:925 msgid "off" msgstr "切" #: option.c:949 msgid "set shell options and positional parameters" msgstr "シェルオプションと位置パラメータを設定する" #: option.c:952 msgid "" "\tset [option...] [--] [new_positional_parameter...]\n" "\tset -o|+o # print current settings\n" msgstr "" "\tset [オプション...] [--] [新しい位置パラメータ...]\n" "\tset -o|+o # 現在の設定を表示する\n" #: parser.c:852 msgid "syntax error: " msgstr "構文エラー: " #: parser.c:876 #, c-format msgid "encountered `%ls' without a matching `('" msgstr "「%ls」に対応する「(」がありません" #: parser.c:878 #, c-format msgid "encountered `%ls' without a matching `{'" msgstr "「%ls」に対応する「{」がありません" #: parser.c:880 #, c-format msgid "`%ls' is used outside `case'" msgstr "「%ls」が case コマンドの外で使われています" #: parser.c:882 parser.c:884 parser.c:2925 #, c-format msgid "`%ls' cannot be used as a command name" msgstr "「%ls」はコマンド名として使えません" #: parser.c:886 parser.c:899 #, c-format msgid "encountered `%ls' without a matching `if' and/or `then'" msgstr "「%ls」に対応する「if」または「then」がありません" #: parser.c:889 #, c-format msgid "encountered `%ls' without a matching `if' or `elif'" msgstr "「%ls」に対応する「if」または「elif」がありません" #: parser.c:891 #, c-format msgid "encountered `%ls' without a matching `for', `while', or `until'" msgstr "「%ls」に対応する「for」「while」または「until」がありません" #: parser.c:894 #, c-format msgid "encountered `%ls' without a matching `do'" msgstr "「%ls」に対応する「do」がありません" #: parser.c:896 #, c-format msgid "encountered `%ls' without a matching `case'" msgstr "「%ls」に対応する「case」がありません" #: parser.c:915 parser.c:2394 #, c-format msgid "(maybe you missed `%ls'?)" msgstr "(「%ls」を忘れていませんか?)" #: parser.c:1218 msgid "the double quotation is not closed" msgstr "二重引用符が閉じられていません" #: parser.c:1235 msgid "the single quotation is not closed" msgstr "単一引用符が閉じられていません" #: parser.c:1392 msgid "the parameter name is missing or invalid" msgstr "パラメータ名が抜けているか不正です" #: parser.c:1404 parser.c:1409 msgid "the index is missing" msgstr "インデックスが抜けています" #: parser.c:1436 parser.c:1449 #, c-format msgid "invalid character `%lc' in parameter expansion" msgstr "パラメータ展開に無効な文字「%lc」が混じっています" #: parser.c:1445 parser.c:1460 parser.c:1504 #, c-format msgid "invalid use of `%lc' in parameter expansion" msgstr "パラメータ展開において「%lc」の使い方が正しくありません" #: parser.c:1603 msgid "the backquoted command substitution is not closed" msgstr "「`」によるコマンド置換が閉じられていません" #: parser.c:1854 msgid "`;' or `&' is missing" msgstr "「;」または「&」が抜けています" #: parser.c:1963 msgid "ksh-like extended glob pattern `!(...)' is not supported" msgstr "ksh の拡張パターン「!(...)」は利用できません" #: parser.c:2064 msgid "a command is missing at the end of input" msgstr "入力の最後でコマンドが抜けています" #: parser.c:2066 #, c-format msgid "a command is missing before `%lc'" msgstr "「%lc」の前にコマンドがありません" #: parser.c:2268 msgid "pipe redirection is not supported in the POSIXly-correct mode" msgstr "パイプリダイレクトは POSIX 準拠モードでは使えません" #: parser.c:2286 msgid "here-string is not supported in the POSIXly-correct mode" msgstr "ヒアストリングは POSIX 準拠モードでは使えません" #: parser.c:2301 msgid "the redirection target is missing" msgstr "リダイレクトの対象が抜けています" #: parser.c:2311 msgid "the end-of-here-document indicator is missing" msgstr "ヒアドキュメントの終端子が抜けています" #: parser.c:2320 msgid "process redirection is not supported in the POSIXly-correct mode" msgstr "プロセスリダイレクトは POSIX 準拠モードでは使えません" #: parser.c:2326 msgid "unclosed process redirection" msgstr "プロセスリダイレクトが閉じられていません" #: parser.c:2337 #, c-format msgid "put a space between `%lc' and `%lc' for disambiguation" msgstr "「%lc」と「%lc」の間に空白が必要です" #: parser.c:2393 msgid "unexpected word after redirection" msgstr "リダイレクトの直後に不正な単語があります" #: parser.c:2428 parser.c:2470 parser.c:2557 parser.c:2601 #, c-format msgid "commands are missing between `%ls' and `%ls'" msgstr "「%ls」と「%ls」の間にコマンドがありません" #: parser.c:2482 parser.c:2591 #, c-format msgid "commands are missing after `%ls'" msgstr "「%ls」の後にコマンドがありません" #: parser.c:2519 msgid "an identifier is required after `for'" msgstr "「for」の後には識別子が必要です" #: parser.c:2521 #, c-format msgid "`%ls' is not a valid identifier" msgstr "「%ls」は有効な識別子ではありません" #: parser.c:2541 msgid "`;' cannot appear on a new line" msgstr "「;」は行頭に置けません" #: parser.c:2630 parser.c:2703 parser.c:2939 #, c-format msgid "a word is required after `%ls'" msgstr "「%ls」の後には単語が必要です" #: parser.c:2693 msgid "an unquoted `esac' cannot be the first case pattern" msgstr "クォートしていない「esac」を最初のパターンにすることはできません" #: parser.c:2706 #, c-format msgid "encountered an invalid character `%lc' in the case pattern" msgstr "case のパターン内に不正な文字「%lc」があります" #: parser.c:2740 msgid "The [[ ... ]] syntax is not supported in the POSIXly-correct mode" msgstr "[[ ... ]] 構文は POSIX 準拠モードでは使えません" #: parser.c:2764 parser.c:2843 #, c-format msgid "invalid word `%ls' between `[[' and `]]'" msgstr "[[ ... ]] の中に不正な単語「%ls」があります" #: parser.c:2897 msgid "conditional expression is missing or incomplete between `[[' and `]]'" msgstr "[[ ... ]] の中の条件式が不完全です" #: parser.c:2903 msgid "unexpected linebreak in the middle of the [[ ... ]] command" msgstr "[[ ... ]] の途中に改行があります" #: parser.c:2906 #, c-format msgid "`%ls' is not a valid operand in the conditional expression" msgstr "「%ls」は条件式における有効な被演算子ではありません" #: parser.c:2971 parser.c:3025 msgid "a function body must be a compound command" msgstr "関数の本体は複合コマンドでなければなりません" #: parser.c:2993 #, c-format msgid "invalid use of `%lc'" msgstr "「%lc」の使い方が正しくありません" #: parser.c:3000 msgid "invalid function name" msgstr "無効な関数名です" #: parser.c:3010 msgid "`(' must be followed by `)' in a function definition" msgstr "関数定義では「(」の直後に「)」が必要です" #: parser.c:3038 msgid "the end-of-here-document indicator contains a newline" msgstr "ヒアドキュメントの終端子に改行が入っています" #: parser.c:3065 parser.c:3099 #, c-format msgid "the here-document content is not closed by `%ls'" msgstr "ヒアドキュメントが「%ls」で閉じられていません" #: parser.c:3151 #, c-format msgid "here-document content for %s%ls is missing" msgstr "%s%ls に対応するヒアドキュメントの内容がありません" #: path.c:1021 msgid "$HOME is not set" msgstr "$HOME が設定されていません" #: path.c:1030 variable.c:3058 msgid "$OLDPWD is not set" msgstr "$OLDPWD が設定されていません" #: path.c:1060 msgid "$PWD has an invalid value" msgstr "$PWD の値が正しくありません" #: path.c:1069 path.c:1080 path.c:1374 msgid "cannot determine the current directory" msgstr "現在の作業ディレクトリが分かりません" #: path.c:1147 #, c-format msgid "`%ls'" msgstr "「%ls」" #: path.c:1178 #, c-format msgid "`%s'" msgstr "「%s」" #: path.c:1325 msgid "change the working directory" msgstr "作業ディレクトリを変更する" #: path.c:1328 msgid "\tcd [-L|-P] [directory]\n" msgstr "\tcd [-L|-P] [ディレクトリ]\n" #: path.c:1386 msgid "print the working directory" msgstr "作業ディレクトリを表示する" #: path.c:1389 msgid "\tpwd [-L|-P]\n" msgstr "\tpwd [-L|-P]\n" #: path.c:1444 #, c-format msgid "no such user `%ls'" msgstr "「%ls」というユーザは存在しません" #: path.c:1466 #, c-format msgid "`%ls': a command name must not contain `/'" msgstr "「%ls」: コマンド名に / を含めることはできません" #: path.c:1474 #, c-format msgid "command `%s' was not found in $PATH" msgstr "コマンド「%s」は $PATH の中に見つかりませんでした" #: path.c:1523 msgid "remember, forget, or report command locations" msgstr "コマンドのパスのキャッシュデータを登録・消去・表示する" #: path.c:1526 msgid "" "\thash command...\n" "\thash -r [command...]\n" "\thash [-a] # print remembered paths\n" "\thash -d user...\n" "\thash -d -r [user...]\n" "\thash -d # print remembered paths\n" msgstr "" "\thash コマンド...\n" "\thash -r [コマンド...]\n" "\thash [-a] # 記憶したパスを表示する\n" "\thash -d ユーザ...\n" "\thash -d -r [ユーザ...]\n" "\thash -d # 記憶したパスを表示する\n" #: path.c:1633 path.c:1716 #, c-format msgid "`%ls' is not a valid mask specification" msgstr "「%ls」は有効なマスク設定ではありません" #: path.c:1743 msgid "print or set the file creation mask" msgstr "ファイル作成マスクを表示または設定する" #: path.c:1746 msgid "" "\tumask mode\n" "\tumask [-S]\n" msgstr "" "\tumask モード\n" "\tumask [-S]\n" #: redir.c:70 #, c-format msgid "error in closing file descriptor %d" msgstr "ファイル記述子 %d を閉じる際にエラー" #: redir.c:91 #, c-format msgid "cannot copy file descriptor %d to %d" msgstr "ファイル記述子 %d を %d にコピーできません" #: redir.c:280 msgid "disabling job control" msgstr "ジョブ制御は無効になります" #: redir.c:328 msgid "redirection: invalid file descriptor" msgstr "リダイレクト: 無効なファイル記述子です" #: redir.c:331 redir.c:639 redir.c:711 #, c-format msgid "redirection: file descriptor %d is unavailable" msgstr "リダイレクト: ファイル記述子 %d は使えません" #: redir.c:380 #, c-format msgid "redirection: cannot open file `%s'" msgstr "リダイレクト: ファイル「%s」を開けません" #: redir.c:469 #, c-format msgid "cannot save file descriptor %d" msgstr "ファイル記述子 %d を保存できません" #: redir.c:589 #, c-format msgid "socket redirection: cannot resolve the address of `%s': %s" msgstr "ソケットリダイレクト:「%s」のアドレスを解決できません: %s" #: redir.c:633 redir.c:648 redir.c:703 #, c-format msgid "redirection: %s" msgstr "リダイレクト: %s" #: redir.c:656 #, c-format msgid "redirection: file descriptor %d is not readable" msgstr "リダイレクト: ファイル記述子 %d は読み込み可能ではありません" #: redir.c:668 #, c-format msgid "redirection: file descriptor %d is not writable" msgstr "リダイレクト: ファイル記述子 %d は書き込み可能ではありません" #: redir.c:706 #, c-format msgid "redirection: %d>>|%d: the input and output file descriptors are same" msgstr "リダイレクト: %d>>|%d: 入力側と出力側のファイル記述子が同じです" #: redir.c:750 #, c-format msgid "redirection: %d>>|%d" msgstr "リダイレクト: %d>>|%d" #: redir.c:768 redir.c:808 redir.c:829 msgid "cannot write the here-document contents to the temporary file" msgstr "ヒアドキュメントの内容を一時ファイルに書き出せません" #: redir.c:821 msgid "cannot create a temporary file for the here-document" msgstr "ヒアドキュメント用の一時ファイルを作成できません" #: redir.c:834 msgid "cannot seek the temporary file for the here-document" msgstr "ヒアドキュメント用の一時ファイルのシークができません" #: redir.c:848 msgid "redirection: cannot open a pipe for the process redirection" msgstr "リダイレクト: プロセスリダイレクト用のパイプを開けません" #: redir.c:887 msgid "process redirection" msgstr "プロセスリダイレクト" #: sig.c:497 msgid "cannot send SIGSTOP signal" msgstr "SIGSTOP シグナルを送信できません" #: sig.c:597 msgid "too many files are opened for yash to handle" msgstr "開いたファイルの数が多すぎて処理しきれません" #: sig.c:803 #, c-format msgid "SIG%ls cannot be trapped" msgstr "SIG%ls はトラップできません" #: sig.c:818 #, c-format msgid "real-time signal SIG%ls is not supported" msgstr "リアルタイムシグナル SIG%ls には対応していません" #: sig.c:834 #, c-format msgid "SIG%ls cannot be reset" msgstr "SIG%ls はリセットできません" #: sig.c:1185 sig.c:1226 sig.c:1304 sig.c:1369 #, c-format msgid "no such signal `%ls'" msgstr "「%ls」というようなシグナルはありません" #: sig.c:1255 msgid "set or print signal handlers" msgstr "シグナルハンドラを設定または表示する" #: sig.c:1258 msgid "" "\ttrap [action signal...]\n" "\ttrap signal_number [signal...]\n" "\ttrap -p [signal...]\n" msgstr "" "\ttrap [動作 シグナル...]\n" "\ttrap シグナル番号 [シグナル...]\n" "\ttrap -p [シグナル...]\n" #: sig.c:1292 msgid "the signal name is not specified" msgstr "シグナル名が指定されていません" #: sig.c:1298 #, c-format msgid "%ls: the signal name must be specified without `SIG'" msgstr "%ls: シグナル名は「SIG」を付けずに指定する必要があります" #: sig.c:1438 msgid "send a signal to processes" msgstr "プロセスにシグナルを送る" #: sig.c:1441 msgid "" "\tkill [-signal|-s signal|-n number] process...\n" "\tkill -l [-v] [number...]\n" msgstr "" "\tkill [-シグナル|-s シグナル|-n シグナル番号] プロセス...\n" "\tkill -l [-v] [数...]\n" #: util.c:253 msgid "unknown error" msgstr "不明なエラー" #: variable.c:373 variable.c:378 msgid "failed to set $PWD" msgstr "$PWD の設定に失敗しました" #: variable.c:402 #, c-format msgid "no such array $%ls" msgstr "$%ls というような配列はありません" #: variable.c:405 variable.c:607 variable.c:1723 variable.c:2346 #: variable.c:3104 #, c-format msgid "$%ls is read-only" msgstr "$%ls は読み込み専用です" #: variable.c:422 #, c-format msgid "failed to unset environment variable $%s" msgstr "環境変数 $%s の削除に失敗しました" #: variable.c:426 #, c-format msgid "failed to set environment variable $%s" msgstr "環境変数 $%s の設定に失敗しました" #: variable.c:701 #, c-format msgid "index %zu is out of range (the actual size of array $%ls is %zu)" msgstr "インデックス %zu は範囲外です (配列 $%ls のサイズは %zu です)" #: variable.c:1237 #, c-format msgid "function `%ls' cannot be redefined because it is read-only" msgstr "関数「%ls」は読み込み専用なので再定義できません" #: variable.c:1420 variable.c:3190 msgid "the directory stack is empty" msgstr "ディレクトリスタックは空です" #: variable.c:1458 variable.c:3042 variable.c:3286 msgid "$PWD is not set" msgstr "$PWD が設定されていません" #: variable.c:1470 #, c-format msgid "index %ls is out of range" msgstr "インデックス %ls は範囲外です" #: variable.c:1747 #, c-format msgid "no such variable $%ls" msgstr "$%ls というような変数はありません" #: variable.c:1762 #, c-format msgid "no such function `%ls'" msgstr "「%ls」というような関数はありません" #: variable.c:1960 msgid "set or print variables" msgstr "変数を設定または表示する" #: variable.c:1963 msgid "\ttypeset [-fgprxX] [name[=value]...]\n" msgstr "\ttypeset [-fgprxX] [名前[=値]...]\n" #: variable.c:1966 msgid "export variables as environment variables" msgstr "変数を環境変数としてエクスポートする" #: variable.c:1969 msgid "\texport [-prX] [name[=value]...]\n" msgstr "\texport [-prX] [名前[=値]...]\n" #: variable.c:1972 msgid "set or print local variables" msgstr "ローカル変数を設定または表示する" #: variable.c:1975 msgid "\tlocal [-prxX] [name[=value]...]\n" msgstr "\tlocal [-prxX] [名前[=値]...]\n" #: variable.c:1978 msgid "make variables read-only" msgstr "変数を読み込み専用にする" #: variable.c:1981 msgid "\treadonly [-fpxX] [name[=value]...]\n" msgstr "\treadonly [-fpxX] [名前[=値]...]\n" #: variable.c:2029 msgid "more than one option cannot be used at once" msgstr "二つ以上のオプションを同時に使うことはできません" #: variable.c:2048 #, c-format msgid "`%ls' is not a valid array name" msgstr "「%ls」は有効な配列名ではありません" #: variable.c:2240 #, c-format msgid "index %ls is out of range (the actual size of array $%ls is %zu)" msgstr "インデックス %ls は範囲外です (配列 $%ls のサイズは %zu です)" #: variable.c:2247 msgid "manipulate an array" msgstr "配列を操作する" #: variable.c:2250 msgid "" "\tarray # print arrays\n" "\tarray name [value...] # set array values\n" "\tarray -d name [index...]\n" "\tarray -i name index [value...]\n" "\tarray -s name index value\n" msgstr "" "\tarray # 配列の一覧を表示する\n" "\tarray 名前 [値...] # 配列に値を設定する\n" "\tarray -d 名前 [インデックス...]\n" "\tarray -i 名前 インデックス [値...]\n" "\tarray -s 名前 インデックス 値\n" #: variable.c:2321 #, c-format msgid "function `%ls' is read-only" msgstr "関数「%ls」は読み込み専用です" #: variable.c:2357 msgid "remove variables or functions" msgstr "変数または関数を削除する" #: variable.c:2360 msgid "\tunset [-fv] [name...]\n" msgstr "\tunset [-fv] 名前...\n" #: variable.c:2403 #, c-format msgid "%ls: the operand value must not be negative" msgstr "%ls: 負でないオペランドの値を指定してください" #: variable.c:2419 variable.c:3101 #, c-format msgid "$%ls is not an array" msgstr "$%ls は配列ではありません" #: variable.c:2436 #, c-format msgid "%ld: cannot shift so many (there is only one positional parameter)" msgid_plural "" "%ld: cannot shift so many (there are only %zu positional parameters)" msgstr[0] "" "%ld: シフトする数が多すぎます (位置パラメータは %zu 個しかありません)" #: variable.c:2442 #, c-format msgid "%ld: cannot shift so many (there is only one array element)" msgid_plural "%ld: cannot shift so many (there are only %zu array elements)" msgstr[0] "%ld: シフトする数が多すぎます (配列の要素は %zu 個しかありません)" #: variable.c:2466 msgid "remove some positional parameters or array elements" msgstr "位置パラメータまたは配列の要素の一部を削除する" #: variable.c:2469 msgid "\tshift [-A array_name] [count]\n" msgstr "\tshift [-A 配列名] [個数]\n" #: variable.c:2500 variable.c:2730 #, c-format msgid "`%ls' is not a valid variable name" msgstr "「%ls」は有効な変数名ではありません" #: variable.c:2503 #, c-format msgid "`%ls' is not a valid option specification" msgstr "「%ls」は有効なオプション指定文字列ではありません" #: variable.c:2561 #, c-format msgid "%ls: `-%lc' is not a valid option\n" msgstr "%ls: 「%lc」は有効なオプションではありません\n" #: variable.c:2584 #, c-format msgid "%ls: the -%lc option's argument is missing\n" msgstr "%ls: -%lc オプションの引数が抜けています\n" #: variable.c:2607 msgid "$OPTIND has an invalid value" msgstr "$OPTIND の値が正しくありません" #: variable.c:2662 msgid "parse command options" msgstr "コマンドのオプションを解析する" #: variable.c:2665 msgid "\tgetopts options variable [argument...]\n" msgstr "\tgetopts オプション 変数名 [引数...]\n" #: variable.c:2973 msgid "read a line from the standard input" msgstr "標準入力から行を読み込む" #: variable.c:2976 msgid "\tread [-Aer] [-P|-p] variable...\n" msgstr "\tread [-Aer] [-P|-p] 変数名...\n" #: variable.c:3156 msgid "push a directory into the directory stack" msgstr "ディレクトリスタックにディレクトリを追加する" #: variable.c:3159 msgid "\tpushd [-L|-P] [directory]\n" msgstr "\tpushd [-L|-P] [ディレクトリ]\n" #: variable.c:3199 variable.c:3275 #, c-format msgid "`%ls' is not a valid index" msgstr "「%ls」は有効なインデックスではありません" #: variable.c:3222 msgid "pop a directory from the directory stack" msgstr "ディレクトリスタックからディレクトリを削除する" #: variable.c:3225 msgid "\tpopd [index]\n" msgstr "\tpopd [インデックス]\n" #: variable.c:3313 msgid "print the directory stack" msgstr "ディレクトリスタックを表示する" #: variable.c:3316 msgid "\tdirs [-cv] [index...]\n" msgstr "\tdirs [-cv] [インデックス...]\n" #: yash.c:112 #, c-format msgid "%s: cannot convert the argument `%s' into a wide character string" msgstr "%s: 引数「%s」をワイド文字列に変換できません" #: yash.c:116 #, c-format msgid "%s: the argument is replaced with an empty string\n" msgstr "%s: 引数は空文字列に置き換えられます\n" #: yash.c:179 msgid "the -c option is specified but no command is given" msgstr "-c オプションが指定されていますがコマンドが与えられていません" #: yash.c:381 #, c-format msgid "" "Syntax:\n" "\t%s [option...] [filename [argument...]]\n" "\t%s [option...] -c command [command_name [argument...]]\n" "\t%s [option...] -s [argument...]\n" msgstr "" "構文:\n" "\t%s [オプション...] [ファイル名 [引数...]]\n" "\t%s [オプション...] -c コマンド [コマンド名 [引数...]]\n" "\t%s [オプション...] -s [引数...]\n" #: yash.c:398 #, c-format msgid "Yet another shell, version %s\n" msgstr "Yet another shell バージョン %s\n" #: yash.c:400 msgid "" "This is free software licensed under GNU GPL version 2.\n" "You can modify and redistribute it, but there is NO WARRANTY.\n" msgstr "" "このソフトは GNU GPL バージョン 2 でライセンスされたフリーソフトウェアで" "す。\n" "自由に変更・再頒布ができますが、ソフトの動作に保証はありません。\n" #: yash.c:404 msgid "" "\n" "Enabled features:\n" msgstr "" "\n" "有効な機能:\n" #: yash.c:546 msgid "Use `exit' to leave the shell.\n" msgstr "シェルを終了するには「exit」コマンドを実行してください。\n" #: yash.c:622 msgid " Use `exit' again to exit anyway.\n" msgstr " それでも終了するにはもう一度「exit」してください。\n" #: yash.c:644 msgid "exit the shell" msgstr "シェルを終了する" #: yash.c:647 msgid "\texit [-f] [exit_status]\n" msgstr "\texit [-f] [終了ステータス]\n" #: yash.c:677 msgid "" "refusing to suspend because of a possible deadlock.\n" "Use the -f option to suspend anyway." msgstr "" "デッドロックの恐れがあるためサスペンドしませんでした。\n" "本当にサスペンドするには -f オプションを付けてください。" #: yash.c:691 msgid "suspend the shell" msgstr "シェルを停止する" #: yash.c:694 msgid "\tsuspend [-f]\n" msgstr "\tsuspend [-f]\n" yash-2.49/po/quot.sed000066400000000000000000000002311354143602500145160ustar00rootroot00000000000000s/"\([^"]*\)"/“\1”/g s/`\([^`']*\)'/‘\1’/g s/ '\([^`']*\)' / ‘\1’ /g s/ '\([^`']*\)'$/ ‘\1’/g s/^'\([^`']*\)' /‘\1’ /g s/“”/""/g yash-2.49/redir.c000066400000000000000000000601431354143602500136740ustar00rootroot00000000000000/* Yash: yet another shell */ /* redir.c: manages file descriptors and provides functions for redirections */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "redir.h" #include #include #include #include #if HAVE_GETTEXT # include #endif #include #if YASH_ENABLE_SOCKET # include #endif #include #include #include #include #include #if YASH_ENABLE_SOCKET # include #endif #include #include #include "exec.h" #include "expand.h" #include "input.h" #include "option.h" #include "parser.h" #include "path.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "yash.h" /********** Utilities **********/ /* Closes the specified file descriptor surely. * If `close' returns EINTR, tries again. * If `close' returns EBADF, it is considered successful and silently ignored. * If `close' returns an error other than EINTR/EBADF, an error message is * printed. */ int xclose(int fd) { while (close(fd) < 0) { switch (errno) { case EINTR: continue; case EBADF: return 0; default: xerror(errno, Ngt("error in closing file descriptor %d"), fd); return -1; } } return 0; } /* Performs `dup2' surely. * If `dup2' returns EINTR, tries again. * If `dup2' returns an error other than EINTR, an error message is printed. * `xclose' is called before `dup2'. */ int xdup2(int oldfd, int newfd) { if (oldfd != newfd) xclose(newfd); while (dup2(oldfd, newfd) < 0) { switch (errno) { case EINTR: continue; default: xerror(errno, Ngt("cannot copy file descriptor %d to %d"), oldfd, newfd); return -1; } } return newfd; } /* Repeatedly calls `write' until all `data' is written. * Returns true iff successful. On error, false is returned with `errno' set. */ /* Note that this function returns a Boolean value, not `ssize_t'. */ bool write_all(int fd, const void *data, size_t size) { while (size > 0) { ssize_t s = write(fd, data, size); if (s < 0) return false; data = (const char *) data + s; size -= s; } return true; } /********** Shell FDs **********/ static void reset_shellfdmin(void); /* True iff the standard input is redirected */ static bool is_stdin_redirected = false; /* Set of file descriptors used by the shell. * These file descriptors cannot be used by the user. */ static fd_set shellfds; /* The minimum file descriptor that can be used for shell FD. */ static int shellfdmin; /* The maximum file descriptor in `shellfds'. * `shellfdmax' is -1 when `shellfds' is empty. */ static int shellfdmax = -1; #ifndef SHELLFDMINMAX #define SHELLFDMINMAX 100 /* maximum for `shellfdmin' */ #endif #if SHELLFDMINMAX < 10 #error SHELLFDMINMAX too little #endif /* File descriptor associated with the controlling terminal */ int ttyfd = -1; /* Initializes shell FDs. */ void init_shellfds(void) { #ifndef NDEBUG static bool initialized = false; assert(!initialized); initialized = true; #endif FD_ZERO(&shellfds); reset_shellfdmin(); assert(shellfdmax == -1); // shellfdmax = -1; } /* Recomputes `shellfdmin'. */ void reset_shellfdmin(void) { errno = 0; shellfdmin = sysconf(_SC_OPEN_MAX); if (shellfdmin < 0) { if (errno != 0) shellfdmin = 10; else shellfdmin = SHELLFDMINMAX; } else { if (shellfdmin > FD_SETSIZE) shellfdmin = FD_SETSIZE; shellfdmin /= 2; if (shellfdmin > SHELLFDMINMAX) shellfdmin = SHELLFDMINMAX; else if (shellfdmin < 10) shellfdmin = 10; } } /* Adds the specified file descriptor (>= `shellfdmin') to `shellfds'. */ void add_shellfd(int fd) { assert(fd >= shellfdmin); if (fd < FD_SETSIZE) FD_SET(fd, &shellfds); if (shellfdmax < fd) shellfdmax = fd; } /* Removes the specified file descriptor from `shellfds'. * Must be called BEFORE `xclose(fd)'. */ void remove_shellfd(int fd) { if (0 <= fd && fd < FD_SETSIZE) FD_CLR(fd, &shellfds); if (fd == shellfdmax) { do shellfdmax--; while (shellfdmax >= 0 && !FD_ISSET(shellfdmax, &shellfds)); } } /* The argument to `FD_CLR' must be a valid (open) file descriptor. This is why * `remove_shellfd' must be called before closing the file descriptor. */ /* Checks if the specified file descriptor is in `shellfds'. */ bool is_shellfd(int fd) { return fd >= FD_SETSIZE || (fd >= 0 && FD_ISSET(fd, &shellfds)); } /* Clears `shellfds'. * If `leavefds' is false, the file descriptors in `shellfds' are closed. */ void clear_shellfds(bool leavefds) { if (!leavefds) { for (int fd = 0; fd <= shellfdmax; fd++) if (FD_ISSET(fd, &shellfds)) xclose(fd); FD_ZERO(&shellfds); shellfdmax = -1; } ttyfd = -1; } /* Duplicates the specified file descriptor as a new shell FD. * The new FD is added to `shellfds'. * On error, `errno' is set and -1 is returned. */ int copy_as_shellfd(int fd) { int newfd; #ifdef F_DUPFD_CLOEXEC /* Even if the F_DUPFD_CLOEXEC flag is defined in the header, the * OS kernel may not support it. We fall back on the normal F_DUPFD-F_SETFD * sequence if the F_DUPFD_CLOEXEC flag is rejected. */ static bool dupfd_cloexec_ok = true; if (dupfd_cloexec_ok) { newfd = fcntl(fd, F_DUPFD_CLOEXEC, shellfdmin); if (newfd >= 0 || errno != EINVAL) goto finish; dupfd_cloexec_ok = false; } #endif newfd = fcntl(fd, F_DUPFD, shellfdmin); if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); #ifdef F_DUPFD_CLOEXEC finish: #endif if (newfd >= 0) add_shellfd(newfd); return newfd; } /* Moves the specified file descriptor (FD) to a shell FD. * The original FD is closed (whether successful or not). * If `fd' is negative, this function simply returns `fd' (without changing * `errno'). If `fd' is non-negative and the FD cannot be copied, `errno' is set * to indicate the error. */ int move_to_shellfd(int fd) { if (fd < 0) return fd; int newfd = copy_as_shellfd(fd); int saveerrno = errno; xclose(fd); errno = saveerrno; return newfd; } /* Opens `ttyfd'. * On failure, an error message is printed and `do_job_control' is set to false. */ void open_ttyfd(void) { if (ttyfd < 0) { ttyfd = move_to_shellfd(open("/dev/tty", O_RDWR)); if (ttyfd < 0) { xerror(errno, Ngt("cannot open file `%s'"), "/dev/tty"); xerror(0, Ngt("disabling job control")); do_job_control = false; } } } /********** Redirections **********/ /* info used to undo redirection */ struct savefd_T { struct savefd_T *next; int sf_origfd; /* original file descriptor */ int sf_copyfd; /* copied file descriptor */ bool sf_stdin_redirected; /* original `is_stdin_redirected' */ }; static char *expand_redir_filename(const struct wordunit_T *filename) __attribute__((malloc,warn_unused_result)); static void save_fd(int oldfd, savefd_T **save) __attribute__((nonnull)); static int open_file(const char *path, int oflag) __attribute__((nonnull)); #if YASH_ENABLE_SOCKET static int open_socket(const char *hostandport, int socktype) __attribute__((nonnull)); #endif static int parse_and_check_dup(char *num, redirtype_T type) __attribute__((nonnull)); static int parse_and_exec_pipe(int outputfd, char *num, savefd_T **save) __attribute__((nonnull)); static int open_heredocument(const struct wordunit_T *content); static int open_herestring(char *s, bool appendnewline) __attribute__((nonnull)); static int open_process_redirection(const embedcmd_T *command, redirtype_T type) __attribute__((nonnull)); /* Opens redirection. * If `save' is non-NULL, the original FD is saved and a pointer to the info is * assigned to `*save' (whether successful or not). * Returns true iff successful. */ bool open_redirections(const redir_T *r, savefd_T **save) { *save = NULL; while (r != NULL) { if (r->rd_fd < 0) { xerror(0, Ngt("redirection: invalid file descriptor")); return false; } else if (is_shellfd(r->rd_fd)) { xerror(0, Ngt("redirection: file descriptor %d is unavailable"), r->rd_fd); return false; } /* expand rd_filename */ char *INIT(filename, ""); switch (r->rd_type) { case RT_INPUT: case RT_OUTPUT: case RT_CLOBBER: case RT_APPEND: case RT_INOUT: case RT_DUPIN: case RT_DUPOUT: case RT_PIPE: case RT_HERESTR: filename = expand_redir_filename(r->rd_filename); if (filename == NULL) return false; break; default: break; } /* save original FD */ save_fd(r->rd_fd, save); /* now, open redirection */ int fd; int flags; bool keepopen; switch (r->rd_type) { case RT_INPUT: flags = O_RDONLY; goto openwithflags; case RT_OUTPUT: if (!shopt_clobber) { flags = O_WRONLY | O_CREAT | O_EXCL; } else { /* falls thru! */ case RT_CLOBBER: flags = O_WRONLY | O_CREAT | O_TRUNC; } goto openwithflags; case RT_APPEND: flags = O_WRONLY | O_CREAT | O_APPEND; goto openwithflags; case RT_INOUT: flags = O_RDWR | O_CREAT; goto openwithflags; openwithflags: keepopen = false; fd = open_file(filename, flags); if (fd < 0) { xerror(errno, Ngt("redirection: cannot open file `%s'"), filename); free(filename); return false; } free(filename); break; case RT_DUPIN: case RT_DUPOUT: keepopen = true; fd = parse_and_check_dup(filename, r->rd_type); if (fd < -1) return false; break; case RT_PIPE: keepopen = false; fd = parse_and_exec_pipe(r->rd_fd, filename, save); if (fd < 0) return false; break; case RT_HERE: case RT_HERERT: keepopen = false; fd = open_heredocument(r->rd_herecontent); if (fd < 0) return false; break; case RT_HERESTR: keepopen = false; fd = open_herestring(filename, true); if (fd < 0) return false; break; case RT_PROCIN: case RT_PROCOUT: keepopen = false; fd = open_process_redirection(&r->rd_command, r->rd_type); if (fd < 0) return false; break; default: assert(false); } /* move the new FD to `r->rd_fd' */ if (fd != r->rd_fd) { if (fd >= 0) { if (xdup2(fd, r->rd_fd) < 0) return false; if (!keepopen) xclose(fd); } else { xclose(r->rd_fd); } } if (r->rd_fd == STDIN_FILENO) is_stdin_redirected = true; r = r->next; } return true; } /* Expands the filename for redirection. * Returns a newly malloced string or NULL. */ char *expand_redir_filename(const struct wordunit_T *filename) { if (is_interactive) { return expand_single_with_glob(filename, TT_SINGLE); } else { wchar_t *result = expand_single_and_unescape( filename, TT_SINGLE, true, false); if (result == NULL) return NULL; char *mbsresult = realloc_wcstombs(result); if (mbsresult == NULL) xerror(EILSEQ, Ngt("redirection")); return mbsresult; } } /* Saves the specified file descriptor if `save' is non-NULL. */ void save_fd(int fd, savefd_T **save) { assert(fd >= 0); int copyfd = copy_as_shellfd(fd); if (copyfd < 0 && errno != EBADF) { xerror(errno, Ngt("cannot save file descriptor %d"), fd); return; } /* If file descriptor `fd' is not open, `copy_as_shellfd' returns -1 with * the EBADF errno value. */ savefd_T *s = xmalloc(sizeof *s); s->next = *save; s->sf_origfd = fd; s->sf_copyfd = copyfd; s->sf_stdin_redirected = is_stdin_redirected; *save = s; } /* Opens the redirected file. * `path' and `oflag' are the first and second arguments to the `open' function. * If `oflag' contains the O_EXCL flag, this function may retry without the flag * to allow opening an existing non-regular file. * If socket redirection is enabled and `path' begins with "/dev/tcp/" or * "/dev/udp/", a socket is opened. * Returns a new file descriptor if successful. Otherwise, `errno' is set and * -1 is returned. */ int open_file(const char *path, int oflag) { const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; start:; int fd = open(path, oflag, mode); // Support the no-clobber mode. if ((oflag & O_EXCL) && fd < 0 && errno == EEXIST) { fd = open(path, oflag & ~(O_CREAT | O_EXCL | O_TRUNC), mode); if (fd < 0) { if (errno == ENOENT) { // A file existed on the first open but not on the second. // Somebody must have removed it between the two opens. // Start over as we might be able to create one this time. goto start; } } else { struct stat st; if (fstat(fd, &st) >= 0 && S_ISREG(st.st_mode)) { // We opened the FD without the O_CREAT flag, so this regular // file was created by somebody else. Failure. xclose(fd); fd = -1; errno = EEXIST; } } } // Support socket redirection. #if YASH_ENABLE_SOCKET if (fd < 0) { const char *hostandport = matchstrprefix(path, "/dev/tcp/"); if (hostandport != NULL) fd = open_socket(hostandport, SOCK_STREAM); } if (fd < 0) { const char *hostandport = matchstrprefix(path, "/dev/udp/"); if (hostandport != NULL) fd = open_socket(hostandport, SOCK_DGRAM); } #endif /* YASH_ENABLE_SOCKET */ return fd; } #if YASH_ENABLE_SOCKET /* Opens a socket. * `hostandport' is the name and the port of the host to connect, concatenated * with a slash. `socktype' specifies the type of the socket, which should be * SOCK_STREAM for TCP or SOCK_DGRAM for UDP. * On failure, returns -1 with `errno' unchanged. */ int open_socket(const char *hostandport, int socktype) { struct addrinfo hints, *ai; int err, saveerrno; char *hostname, *port; int fd; saveerrno = errno; /* decompose `hostandport' into `hostname' and `port' */ { wchar_t *whostandport; const wchar_t *wport; whostandport = malloc_mbstowcs(hostandport); if (whostandport == NULL) { errno = saveerrno; return -1; } wport = wcschr(whostandport, L'/'); if (wport != NULL) { hostname = malloc_wcsntombs(whostandport, wport - whostandport); port = malloc_wcstombs(wport + 1); // XXX error ignored } else { hostname = xstrdup(hostandport); port = NULL; } free(whostandport); } set_interruptible_by_sigint(true); hints.ai_flags = 0; hints.ai_family = AF_UNSPEC; hints.ai_socktype = socktype; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; hints.ai_next = NULL; err = getaddrinfo(hostname, port, &hints, &ai); free(hostname); free(port); if (err != 0) { xerror(0, Ngt("socket redirection: " "cannot resolve the address of `%s': %s"), hostandport, gai_strerror(err)); set_interruptible_by_sigint(false); errno = saveerrno; return -1; } fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd >= 0 && connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { xclose(fd); fd = -1; } saveerrno = errno; freeaddrinfo(ai); set_interruptible_by_sigint(false); errno = saveerrno; return fd; } #endif /* YASH_ENABLE_SOCKET */ /* Parses the argument to an RT_DUPIN/RT_DUPOUT redirection. * `num' is the argument to parse, which is expected to be "-" or a non-negative * integer. `num' is freed in this function. * If `num' is a non-negative integer, the value is returned. * If `num' is "-", -1 is returned. * Otherwise, a negative value other than -1 is returned. * `type' must be either RT_DUPIN or RT_DUPOUT. */ int parse_and_check_dup(char *const num, redirtype_T type) { int fd; if (strcmp(num, "-") == 0) { fd = -1; goto end; } if (!xisxdigit(num[0])) errno = EINVAL; else if (xstrtoi(num, 10, &fd)) if (fd < 0) errno = ERANGE; if (errno != 0) { xerror(errno, Ngt("redirection: %s"), num); fd = -2; goto end; } if (is_shellfd(fd)) { xerror(0, Ngt("redirection: file descriptor %d is unavailable"), fd); fd = -2; goto end; } if (posixly_correct) { /* check the read/write permission */ int flags = fcntl(fd, F_GETFL); if (flags < 0) { xerror(errno, Ngt("redirection: %s"), num); fd = -2; } else if (type == RT_DUPIN) { switch (flags & O_ACCMODE) { case O_RDONLY: case O_RDWR: /* ok */ break; default: xerror(0, Ngt("redirection: " "file descriptor %d is not readable"), fd); fd = -2; break; } } else { assert(type == RT_DUPOUT); switch (flags & O_ACCMODE) { case O_WRONLY: case O_RDWR: /* ok */ break; default: xerror(0, Ngt("redirection: " "file descriptor %d is not writable"), fd); fd = -2; break; } } } end: free(num); return fd; } /* Parses the argument to an RT_PIPE redirection and opens a pipe. * `outputfd' is the file descriptor of the output side of the pipe. * `num' is the argument to parse, which is expected to be a non-negative * integer that is the file descriptor of the input side of the pipe. * `num' is freed in this function. * The input side FD is saved in this function. * If successful, the actual file descriptor of the output side of the pipe is * returned, which may differ from `outputfd'. Otherwise, -1 is returned. */ int parse_and_exec_pipe(int outputfd, char *num, savefd_T **save) { int fd, inputfd; int pipefd[2]; assert(outputfd >= 0); if (!xisxdigit(num[0])) { errno = EINVAL; } else { if (xstrtoi(num, 10, &inputfd) && inputfd < 0) errno = ERANGE; } if (errno != 0) { xerror(errno, Ngt("redirection: %s"), num); fd = -1; } else if (outputfd == inputfd) { xerror(0, Ngt("redirection: %d>>|%d: " "the input and output file descriptors are same"), outputfd, inputfd); fd = -1; } else if (is_shellfd(inputfd)) { xerror(0, Ngt("redirection: file descriptor %d is unavailable"), inputfd); fd = -1; } else { /* ok, save inputfd and open the pipe */ save_fd(inputfd, save); if (pipe(pipefd) < 0) goto error; /* move the output side from what is to be the input side. */ if (pipefd[PIPE_OUT] == inputfd) { int newfd = dup(pipefd[PIPE_OUT]); if (newfd < 0) goto error2; xclose(pipefd[PIPE_OUT]); pipefd[PIPE_OUT] = newfd; } /* move the input side to where it should be. */ if (pipefd[PIPE_IN] != inputfd) { if (xdup2(pipefd[PIPE_IN], inputfd) < 0) goto error2; xclose(pipefd[PIPE_IN]); // pipefd[PIPE_IN] = inputfd; } /* The output side is not moved in this function. */ fd = pipefd[PIPE_OUT]; } end: free(num); return fd; error2:; int saveerrno = errno; xclose(pipefd[PIPE_IN]); xclose(pipefd[PIPE_OUT]); errno = saveerrno; error: xerror(errno, Ngt("redirection: %d>>|%d"), outputfd, inputfd); fd = -1; goto end; } /* Opens a here-document whose contents is specified by the argument. * Returns a newly opened file descriptor if successful, or -1 on error. */ /* The contents of the here-document is passed either through a pipe or a * temporary file. */ int open_heredocument(const wordunit_T *contents) { wchar_t *wcontents = expand_single_and_unescape( contents, TT_NONE, false, false); if (wcontents == NULL) return -1; char *mcontents = realloc_wcstombs(wcontents); if (mcontents == NULL) { xerror(EILSEQ, Ngt("cannot write the here-document contents " "to the temporary file")); return -1; } return open_herestring(mcontents, false); } /* Opens a here-string whose contents is specified by the argument. * If `appendnewline' is true, a newline is appended to the value of `s'. * Returns a newly opened file descriptor if successful, or -1 on error. * `s' is freed in this function. */ /* The contents of the here-document is passed either through a pipe or a * temporary file. */ int open_herestring(char *s, bool appendnewline) { int fd; /* if contents is empty */ if (s[0] == '\0' && !appendnewline) { fd = open("/dev/null", O_RDONLY); if (fd >= 0) { free(s); return fd; } } size_t len = strlen(s); if (appendnewline) s[len++] = '\n'; #ifdef PIPE_BUF /* use a pipe if the contents is short enough */ if (len <= PIPE_BUF) { int pipefd[2]; if (pipe(pipefd) >= 0) { /* It is guaranteed that all the contents is written to the pipe * at once, so we don't have to use `write_all' here. */ if (write(pipefd[PIPE_OUT], s, len) < 0) xerror(errno, Ngt("cannot write the here-document contents " "to the temporary file")); xclose(pipefd[PIPE_OUT]); free(s); return pipefd[PIPE_IN]; } } #endif /* defined(PIPE_BUF) */ char *tempfile; fd = create_temporary_file(&tempfile, "", 0); if (fd < 0) { xerror(errno, Ngt("cannot create a temporary file for the here-document")); free(s); return -1; } if (unlink(tempfile) < 0) xerror(errno, Ngt("failed to remove temporary file `%s'"), tempfile); free(tempfile); if (!write_all(fd, s, len)) xerror(errno, Ngt("cannot write the here-document contents " "to the temporary file")); free(s); if (lseek(fd, 0, SEEK_SET) != 0) xerror(errno, Ngt("cannot seek the temporary file for the here-document")); return fd; } /* Opens process redirection and returns the file descriptor. * `type' must be RT_PROCIN or RT_PROCOUT. * The return value is -1 if failed. */ int open_process_redirection(const embedcmd_T *command, redirtype_T type) { int pipefd[2]; pid_t cpid; assert(type == RT_PROCIN || type == RT_PROCOUT); if (pipe(pipefd) < 0) { xerror(errno, Ngt("redirection: cannot open a pipe " "for the process redirection")); return -1; } cpid = fork_and_reset(-1, false, 0); if (cpid < 0) { /* fork failure */ xclose(pipefd[PIPE_IN]); xclose(pipefd[PIPE_OUT]); return -1; } else if (cpid) { /* parent process */ if (type == RT_PROCIN) { xclose(pipefd[PIPE_OUT]); return pipefd[PIPE_IN]; } else { xclose(pipefd[PIPE_IN]); return pipefd[PIPE_OUT]; } } else { /* child process */ if (type == RT_PROCIN) { xclose(pipefd[PIPE_IN]); if (pipefd[PIPE_OUT] != STDOUT_FILENO) { if (xdup2(pipefd[PIPE_OUT], STDOUT_FILENO) < 0) exit(Exit_NOEXEC); xclose(pipefd[PIPE_OUT]); } } else { xclose(pipefd[PIPE_OUT]); if (pipefd[PIPE_IN] != STDIN_FILENO) { if (xdup2(pipefd[PIPE_IN], STDIN_FILENO) < 0) exit(Exit_NOEXEC); xclose(pipefd[PIPE_IN]); } } if (command->is_preparsed) exec_and_or_lists(command->value.preparsed, true); else exec_wcs(command->value.unparsed, gt("process redirection"), true); assert(false); } } /* Restores the saved file descriptor and frees `save'. */ void undo_redirections(savefd_T *save) { while (save != NULL) { if (save->sf_copyfd >= 0) { remove_shellfd(save->sf_copyfd); xdup2(save->sf_copyfd, save->sf_origfd); xclose(save->sf_copyfd); } else { xclose(save->sf_origfd); } is_stdin_redirected = save->sf_stdin_redirected; savefd_T *next = save->next; free(save); save = next; } } /* Frees the FD-saving info without restoring FD. * The copied FDs are closed. */ void clear_savefd(savefd_T *save) { while (save != NULL) { if (save->sf_copyfd >= 0) { remove_shellfd(save->sf_copyfd); xclose(save->sf_copyfd); } savefd_T *next = save->next; free(save); save = next; } } /* Redirects the standard input to "/dev/null" if job control is off and the * standard input is not yet redirected. */ void maybe_redirect_stdin_to_devnull(void) { int fd; if (do_job_control || is_stdin_redirected) return; if (xclose(STDIN_FILENO) < 0) return; fd = open("/dev/null", O_RDONLY); if (fd < 0) { //xerror(errno, Ngt("cannot redirect the standard input to /dev/null")); } else { assert(fd == STDIN_FILENO); } is_stdin_redirected = true; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/redir.h000066400000000000000000000035431354143602500137020ustar00rootroot00000000000000/* Yash: yet another shell */ /* redir.h: manages file descriptors and provides functions for redirections */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_REDIR_H #define YASH_REDIR_H #include extern int xclose(int fd); extern int xdup2(int oldfd, int newfd); extern _Bool write_all(int fd, const void *data, size_t size) __attribute__((nonnull)); extern int ttyfd; extern void init_shellfds(void); extern void add_shellfd(int fd); extern void remove_shellfd(int fd); extern _Bool is_shellfd(int fd) __attribute__((pure)); extern void clear_shellfds(_Bool leavefds); extern int copy_as_shellfd(int fd); extern int move_to_shellfd(int fd); extern void open_ttyfd(void); extern int get_ttyfd(void) __attribute__((pure)); typedef struct savefd_T savefd_T; struct redir_T; extern _Bool open_redirections(const struct redir_T *r, savefd_T **save) __attribute__((nonnull(2))); extern void undo_redirections(savefd_T *save); extern void clear_savefd(savefd_T *save); extern void maybe_redirect_stdin_to_devnull(void); #define PIPE_IN 0 /* index of the reading end of a pipe */ #define PIPE_OUT 1 /* index of the writing end of a pipe */ #endif /* YASH_REDIR_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/refcount.h000066400000000000000000000024771354143602500144270ustar00rootroot00000000000000/* Yash: yet another shell */ /* refcount.h: reference counter utility */ /* (C) 2015 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_REFCOUNT_H #define YASH_REFCOUNT_H #include #include "util.h" #ifdef UINTPTR_MAX typedef uintptr_t refcount_T; #else typedef uintmax_t refcount_T; #endif static inline void refcount_increment(refcount_T *r) __attribute__((nonnull)); static inline _Bool refcount_decrement(refcount_T *r) __attribute__((nonnull)); void refcount_increment(refcount_T *rc) { (*rc)++; if (*rc == 0) alloc_failed(); } _Bool refcount_decrement(refcount_T *rc) { (*rc)--; return *rc == 0; } #endif /* YASH_REFCOUNT_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/share/000077500000000000000000000000001354143602500135215ustar00rootroot00000000000000yash-2.49/share/completion/000077500000000000000000000000001354143602500156725ustar00rootroot00000000000000yash-2.49/share/completion/INIT000066400000000000000000000443471354143602500163740ustar00rootroot00000000000000# (C) 2010-2019 magicant # This file is autoloaded when completion is first performed by the shell. # This file contains utility functions that are commonly used by many # completion functions. # This is the function that is called when completing a command argument. if ! command -vf completion//argument >/dev/null 2>&1; then function completion//argument { command -f completion//reexecute -f } fi # This function parses array $WORDS and variable $TARGETWORD according to array # $OPTIONS and modifies $WORDS so that the caller can easily parse $WORDS. # # $WORDS is an array containing command line words that have been already # entered by the user before the word being completed. The first word of $WORDS # is considered the command name and ignored. The other words are considered # arguments to the command and parsed. # # $TARGETWORD is a variable containing the word being completed. # # $OPTIONS is an array containing data about parsed options. The value of each # element must follow the following syntax. # element := optionlist # | optionlist ";" description # optionlist := option # | optionlist " " option # option := optionvalue # | optionvalue ":" # | optionvalue "::" # optionvalue := character # | "-" characters # An element value consists of an option list that is possibly followed by a # description of the options. The option list and the description is separated # by a semicolon and the description may contain any characters. # The option list consists of any number of options separated by whitespaces. # An option is an option value possibly followed by one or two colons. # An option that takes no, mandatory, and optional argument should be specified # with zero, one, and two colons, respectively. # The option value may be either a single character or a character string # preceded by one or more hyphens. Note that a single-character option must be # specified without a hyphen though it is preceded by a hyphen on the command # line. Also note that multiple single-character options can be combined # together after one hyphen on the command line. Long options, on the other # hand, must be specified including their preceding hyphen(s). # # The following options can be specified to this function: # -e Without -e, options are parsed only before the first operand word in # $WORDS. Words after the first operand word are all considered operands. # With -e, options are parsed for any words in $WORDS (except after # "--"). Options and operands can be intermixed. # -n Without -n, array $WORDS is updated to reflect the parse result. # Multiple single-character options after one hyphen are separated and # separate option arguments are combined with the options so that the # option and its argument are in the same command line word. When -s is # specified or -e is not specified, options and operands in $WORDS are # separated by "--". # With -n, $WORDS is left intact. # -s Only meaningful when with -e and without -n. # Without -s, words in $WORDS are not reordered. # With -s, words in $WORDS are reordered so that all options come # before operands. # # After words in $WORDS are parsed, $TARGETWORD is parsed as well and it is # determined how $TARGETWORD should be completed. The results are returned as # two variables $ARGOPT and $PREFIX. # If $TARGETWORD is an operand to the command, $ARGOPT and $PREFIX are empty # strings. If $TARGETWORD is one or more single-character option, $ARGOPT is # "-" and $PREFIX is $TARGETWORD. If $TARGETWORD is a long option or "-", # $ARGOPT is "-" and $PREFIX is an empty string. If $TARGETWORD is an option # followed by an argument to that option or a separate option argument (in # which case $TARGETWORD should be completed as an option argument), $ARGOPT # is the name of that option (as specified by "optionvalue" in the syntax # above) and $PREFIX is the part of $TARGETWORD that is not the argument. # # Note: # Single-hyphened long options cannot be abbreviated. # Optional option arguments are not supported for single-hyphened long options. # Ambiguous options, invalid options, etc. are silently ignored. function completion//parseoptions { # parse options to this function itself typeset opt= OPTIND=1 typeset extension=false update=true sort=false while getopts :ens opt; do case $opt in (e) extension=true;; (n) update=false;; (s) sort=true;; esac done if [ "${WORDS+set}" != "set" ] || [ ${WORDS[#]} -le 0 ] || [ "${OPTIONS+set}" != "set" ] || [ "${TARGETWORD+set}" != "set" ]; then return 1 fi # results typeset result options operands result=() options=() operands=() # parse $WORDS typeset index=1 nomoreoptions=false typeset matches ARGOPT= PREFIX= while index=$((index+1)); [ $index -le ${WORDS[#]} ]; do typeset word="${WORDS[index]}" case $word in (--) result=("$result" "${WORDS[index,-1]}") operands=("$operands" "${WORDS[index+1,-1]}") nomoreoptions=true break ;; (--?*=*) # double-hyphened long option with argument matches=() for opt in ${OPTIONS%%;*}; do case ${${opt%:}%:} in ("${word%%=*}") matches=("$opt") break ;; ("${word%%=*}"*) matches=("$matches" "$opt") ;; esac done if [ ${matches[#]} -eq 1 ]; then opt=${matches[1]} case $opt in (*:) # option argument allowed opt=${${opt%:}%:} word=${word#*=} result=("$result" "$opt=$word") options=("$options" "$opt=$word") esac fi ;; (--?*) # double-hyphened long option without argument matches=() for opt in ${OPTIONS%%;*}; do case ${${opt%:}%:} in ("$word") matches=("$opt") break ;; ("$word"*) matches=("$matches" "$opt") ;; esac done if [ ${matches[#]} -eq 1 ]; then opt=${matches[1]} case $opt in (*[!:]:) # option argument required if [ $index -eq ${WORDS[#]} ]; then # $TARGETWORD is argument ARGOPT=${opt%:} # PREFIX= break else index=$((index+1)) # ${WORDS[index]} is argument result=("$result" "${opt%:}=${WORDS[index]}") options=("$options" "${opt%:}=${WORDS[index]}") fi ;; (*) # no option argument result=("$result" "${opt%::}") options=("$options" "${opt%::}") ;; esac fi ;; (-?*) # single-hyphened option # first check for single-hyphened long option for opt in ${OPTIONS%%;*}; do case ${${opt%:}%:} in ("${word%%=*}") case $opt in (*::) # optional argument not supported ;; (*:) # requires option argument case $word in (*=*) result=("$result" "$word") options=("$options" "$word") ;; (*) # argument is next word if [ $index -eq ${WORDS[#]} ]; then # $TARGETWORD is argument ARGOPT=${opt%:} # PREFIX= break 2 else index=$((index+1)) # ${WORDS[index]} is argument result=("$result" "${opt%:}=${WORDS[index]}") options=("$options" "${opt%:}=${WORDS[index]}") fi ;; esac ;; (*) # no option argument case $word in (*=*) # Bad! ;; (*) # OK! result=("$result" "$opt") options=("$options" "$opt") ;; esac ;; esac continue 2 esac done # Next, check for single-character options while word=${word#?}; [ "$word" ]; do for opt in ${OPTIONS%%;*}; do case $opt in ("${word[1]}") # no option argument result=("$result" "-$opt") options=("$options" "-$opt") ;; ("${word[1]}":) # requires option argument if [ "${word#?}" ]; then # rest of $word is argument result=("$result" "-$word") options=("$options" "-$word") elif [ $index -eq ${WORDS[#]} ]; then # $TARGETWORD is argument ARGOPT=${opt%:} # PREFIX break 3 else index=$((index+1)) # ${WORDS[index]} is argument result=("$result" "-${opt%:}${WORDS[index]}") options=("$options" "-${opt%:}${WORDS[index]}") fi break 2 ;; ("${word[1]}"::) # optional option argument result=("$result" "-$word") options=("$options" "-$word") break 2 ;; esac done done ;; (*) # operand if $extension; then result=("$result" "$word") operands=("$operands" "$word") else result=("$result" "${WORDS[index,-1]}") operands=("$operands" "${WORDS[index,-1]}") nomoreoptions=true break fi ;; esac done # determine if $TARGETWORD should be completed as an option if ! $nomoreoptions && [ -z "$ARGOPT" ]; then case $TARGETWORD in (--*=*) matches=() for opt in ${OPTIONS%%;*}; do case ${${opt%:}%:} in ("${TARGETWORD%%=*}") matches=("$opt") break ;; ("${TARGETWORD%%=*}"*) matches=("$matches" "$opt") ;; esac done if [ ${matches[#]} -eq 1 ]; then opt=${matches[1]} case $opt in (*:) # option argument allowed ARGOPT=${${opt%:}%:} PREFIX=${TARGETWORD%%=*}= esac fi ;; (-|--*) ARGOPT=- # PREFIX= ;; (-*) ARGOPT=- # PREFIX= # first check for single-hyphened long option typeset long=false for opt in ${OPTIONS%%;*}; do case ${opt%:} in ("${TARGETWORD%%=*}"*) long=true case $TARGETWORD in (*=*) if [ "${TARGETWORD%%=*}" = "${opt%:}" ]; then ARGOPT=${opt%:} PREFIX=${TARGETWORD%%=*}= break fi esac esac done # Next, check for single-character options if ! $long; then typeset word="$TARGETWORD" while word=${word#?}; [ "$word" ]; do for opt in ${OPTIONS%%;*}; do case $opt in ("${word[1]}") result=("$result" "-$opt") options=("$options" "-$opt") ;; ("${word[1]}":|"${word[1]}"::) ARGOPT=${${opt%:}%:} PREFIX=${TARGETWORD%${word#?}} break 2 ;; esac done done if [ -z "$word" ]; then PREFIX=$TARGETWORD fi fi ;; esac fi # update $WORDS if $update; then if ! $extension || $sort; then WORDS=("${WORDS[1]}" "$options" -- "$operands") else WORDS=("${WORDS[1]}" "$result") fi fi } # This function calls the "complete" built-in to generate candidates to # complete $TARGETWORD as an option. Candidates are generated according to # array $OPTIONS specifying options in the same syntax as the # "completion//parseoptions" function above. If more than one option matches # $TARGETWORD, only the first match is used to generate the candidate. # # This function accepts either of the -s and -S options. With -s, this function # completes single-character options only (in which case $TARGETWORD may have # other single-character options already entered). With -S, $TARGETWORD is # completed as a single option. These options are mutually exclusive. If # specified both, the last specified one is effective. If specified neither, # it defaults to whether $PREFIX is empty or not. # # Normally, a long option that takes an argument is completed with an `=' sign. # But if you specify the -e option, the option is completed with a space like a # normal word. # # The exit status of this function is 0 if at least one candidate was # generated. Otherwise, the exit status is 1. # # If variable $ARGOPT is defined but its value is not "-" or if $TARGETWORD # does not start with a hyphen, this function does nothing but returning the # exit status of 2 unless the -f option is specified. function completion//completeoptions { # check $PREFIX if [ "${PREFIX-}" ]; then typeset single=true else typeset single=false fi # parse options to this function itself typeset opt= OPTIND=1 longargeq=true force=false while getopts :efsS opt; do case $opt in (e) longargeq=;; (f) force=true;; (s) single=true;; (S) single=false;; esac done if ! $force; then if [ "${OPTIONS+set}" != "set" ] || [ "${TARGETWORD+set}" != "set" ] || [ "${ARGOPT--}" != "-" ]; then return 2 fi case $TARGETWORD in (-*) ;; (*) return 2 ;; esac fi # generate candidates typeset opts desc typeset generated=false for opts in "$OPTIONS"; do # get description case $opts in (*\;*) desc=${opts#*;} while true; do # trim surrounding spaces case $desc in ([[:space:]]*) desc=${desc#[[:space:]]} ;; (*[[:space:]]) desc=${desc%[[:space:]]} ;; (*) break ;; esac done ;; (*) desc= ;; esac if $single; then # generate single-character option for opt in ${opts%%;*}; do case $opt in ([!-]|[!-]:) complete -O -P "$TARGETWORD" ${desc:+-D "$desc"} "${opt%:}" && generated=true break ;; ([!-]::) complete -OT -P "$TARGETWORD" ${desc:+-D "$desc"} "${opt%::}" && generated=true break ;; esac done else # generate candidate for first match for opt in ${opts%%;*}; do case $opt in (-*:) # long option that takes argument case ${${opt%:}%:} in ("$TARGETWORD"*) complete ${desc:+-D "$desc"} ${longargeq:+-T -S =} -O -- "${${opt%:}%:}" && generated=true break esac ;; (-*) # long option that does not take argument case $opt in ("$TARGETWORD"*) complete ${desc:+-D "$desc"} -O -- "$opt" && generated=true break esac ;; (?::) # single-char option w/ optional argument if [ "$TARGETWORD" = "-" ]; then complete ${desc:+-D "$desc"} -OT -- "-${opt%::}" && generated=true break fi ;; (?|?:) # other single-char option if [ "$TARGETWORD" = "-" ]; then complete ${desc:+-D "$desc"} -O -- "-${opt%:}" && generated=true break fi ;; esac done fi done # return 0 if at least one candidate was generated $generated } # This function removes one or more elements from the $WORDS array. # When this function is called, the array is supposed to contain the following # elements (in this order): # * a command name word # * any number of options that start with a hyphen # * a "--" separator (optional) # * any number of operands # This function removes any words other than operands. function completion//getoperands { typeset i=2 while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) i=$((i+1)) break ;; (-?*) i=$((i+1)) ;; (*) break ;; esac done WORDS=("${WORDS[i,-1]}") } # This function re-executes the completion function using the current $WORDS. # If the -e option is specified, only external commands are completed as the # command name. # If the -f option is specified, the following fall-back functions are not used: # * completion//command # * completion//argument # If an operand is given to this function, it is used as the command name # instead of ${WORDS[1]} and the -f option is assumed. # This function does not change $WORDS (but the completion function may do). function completion//reexecute { # parse options typeset opt= OPTIND=1 extonly=false fallback=true while getopts :ef opt; do case $opt in (e) extonly=true;; (f) fallback=false;; esac done # if there's no words, complete the command name if [ ${WORDS[#]} -le 0 ]; then if $fallback && command -vf completion//command >/dev/null 2>&1; then unset opt OPTIND extonly fallback command -f completion//command else complete -P "${PREFIX-}" -T -S / -d case ${TARGETWORD#"${PREFIX-}"} in (*/*) complete -P "${PREFIX-}" --executable-file ;; (*) if $extonly; then complete -P "${PREFIX-}" --external-command else complete -P "${PREFIX-}" -R '*/*' -ck --normal-alias fi ;; esac fi return fi if [ $OPTIND -le $# ]; then set -- "${@[OPTIND]}" fallback=false else set -- "${WORDS[1]}" fi if $fallback && command -vf completion//argument >/dev/null 2>&1; then unset opt OPTIND extonly fallback command -f completion//argument return fi typeset compfunc= cmdbase if ! command -f completion//findcompfunc "$1"; then cmdbase="${1%.*}" if [ "$cmdbase" != "$1" ] && [ "${PATHEXT:+set}" = set ] && grep -iqx "${{PATHEXT//';'/'\|'}//'.'/'\.'}" <<< "${1#"$cmdbase"}" && command -f completion//findcompfunc "$cmdbase" && [ $OPTIND -gt $# ]; then WORDS=("$cmdbase" "${WORDS[2,-1]}") fi fi set -- "$compfunc" unset opt OPTIND extonly fallback compfunc cmdbase if [ "$1" ]; then command -f "$1" else complete -P "${PREFIX-}" -f fi } # $1 = command name # The result is set to the "compfunc" variable function completion//findcompfunc if command -vf "completion/$1" >/dev/null 2>&1; then compfunc="completion/$1" elif command -vf "completion/${1##*/}" >/dev/null 2>&1; then compfunc="completion/${1##*/}" elif . -AL "completion/$1" 2>/dev/null command -vf "completion/$1" >/dev/null 2>&1; then compfunc="completion/$1" elif command -vf "completion/${1##*/}" >/dev/null 2>&1; then compfunc="completion/${1##*/}" elif . -AL "completion/${1##*/}" 2>/dev/null command -vf "completion/$1" >/dev/null 2>&1; then compfunc="completion/$1" elif command -vf "completion/${1##*/}" >/dev/null 2>&1; then compfunc="completion/${1##*/}" else compfunc= false fi # Prints all commands in $PATH. # Given an argument, it is treated as a pattern that filters results. function completion//allcommands { typeset IFS # check if $PATH is an array case $(typeset -p PATH 2>/dev/null) in (PATH=\(*) ;; ('') typeset PATH PATH=() ;; (*) # re-define $PATH as local array typeset IFS=: savepath="$PATH" typeset PATH PATH=($savepath) ;; esac IFS=' ' # use "find" if it supports "-min/maxdepth" if find -L . -mindepth 0 -maxdepth 0 -prune >&2; then find -L "$PATH" -mindepth 1 -maxdepth 1 \ ${1+-name "$1"} -type f -perm -u+x return fi 2>/dev/null typeset dir file pat="${1-*}" oldopt="$(set +o)" set -o glob -o dotglob -o nullglob -o caseglob -o markdirs for dir in "$PATH"; do case $dir in (*/) ;; (*) dir=$dir/ ;; esac for file in "$dir"*; do case $file in (*/.|*/..|*/) ;; (*/$pat) if [ -x "$file" ]; then printf '%s\n' "$file" fi ;; esac done done eval "$oldopt" } # The completion function for the "." built-in if ! command -vf completion/. >/dev/null 2>&1; then function completion/. { # load the "_dot" file and call the "completion/." function in it . -AL completion/_dot && command -f completion/. "$@" } fi # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/[000066400000000000000000000002621354143602500160270ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "[" built-in command. function completion/[ { command -f completion//reexecute test } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/_backup000066400000000000000000000007521354143602500172250ustar00rootroot00000000000000# (C) 2010 magicant # This file contains a function that completes the argument to the --backup # option of the cp, ln, and mv commands. function completion//completebackup { #>># complete -P "$PREFIX" -D "don't make backups" none off complete -P "$PREFIX" -D "make numbered backups" numbered t complete -P "$PREFIX" -D "make backups according to existing ones" existing nil complete -P "$PREFIX" -D "make simple backups" simple never } #<<# # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/_blocksize000066400000000000000000000044561354143602500177520ustar00rootroot00000000000000# (C) 2010-2013 magicant # This file contains functions that help completion of file size suffixes. # Modifies the $PREFIX variable to include argument digits. # If $TARGETWORD does not start with a digit, a non-zero status is returned. function completion//prefixdigits { typeset word="${TARGETWORD#"$PREFIX"}" word=${word#[\'+-]} case $word in ([[:digit:]]*) while [ "${word#[[:digit:]]}" != "$word" ]; do word=${word#[[:digit:]]} done PREFIX=${TARGETWORD%"$word"} return 0 ;; (*) PREFIX=${TARGETWORD%"$word"} return 1 ;; esac } # Completes file size suffixes specified in arguments according to the current # $TARGETWORD and $PREFIX. function completion//completesizesuffix { typeset arg for arg do case $arg in (b) #>># complete -P "$PREFIX" -D "512 bytes" b ;; #<<# (k) #>># complete -P "$PREFIX" -D "1024 bytes" k ;; #<<# (m) #>># complete -P "$PREFIX" -D "1024^2 bytes" m ;; #<<# (g) #>># complete -P "$PREFIX" -D "1024^3 bytes" g ;; #<<# (KMGB) #>># complete -P "$PREFIX" -D "1024 bytes" K complete -P "$PREFIX" -D "1000 bytes" KB complete -P "$PREFIX" -D "1024^2 bytes" M complete -P "$PREFIX" -D "1000^2 bytes" MB complete -P "$PREFIX" -D "1024^3 bytes" G complete -P "$PREFIX" -D "1000^3 bytes" GB ;; #<<# (GNU*) #>># command -f completion//completesizesuffix KMGB complete -P "$PREFIX" -D "1024^4 bytes" T complete -P "$PREFIX" -D "1000^4 bytes" TB complete -P "$PREFIX" -D "1024^5 bytes" P complete -P "$PREFIX" -D "1000^5 bytes" PB complete -P "$PREFIX" -D "1024^7 bytes" Z complete -P "$PREFIX" -D "1000^7 bytes" ZB complete -P "$PREFIX" -D "1024^8 bytes" Y complete -P "$PREFIX" -D "1000^8 bytes" YB #<<# if [ "$arg" != GNU-E ]; then #>># complete -P "$PREFIX" -D "1024^6 bytes" E complete -P "$PREFIX" -D "1000^6 bytes" EB fi #<<# ;; esac done return 0 } # Completes the argument to the --block-size option of df, du, and ls commands. function completion//completeblocksize { if ! command -f completion//prefixdigits; then #>># complete -P "$PREFIX" -D "print size using K, M, etc. for 1024^n" human-readable complete -P "$PREFIX" -D "print size using k, M, etc. for 1000^n" si fi #<<# command -f completion//completesizesuffix GNU } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/_bsd000066400000000000000000000020721354143602500165250ustar00rootroot00000000000000# (C) 2011 magicant # This file contains auxiliary functions that handle BSD-specific features. # This function completes authentication methods function completion//bsd::completeauthmethod { # TODO description #>># complete -P "$PREFIX" activ complete -P "$PREFIX" chpass complete -P "$PREFIX" crypto complete -P "$PREFIX" krb5 complete -P "$PREFIX" krb5-or-pwd complete -P "$PREFIX" lchpass complete -P "$PREFIX" passwd complete -P "$PREFIX" radius complete -P "$PREFIX" reject complete -P "$PREFIX" rpasswd complete -P "$PREFIX" skey complete -P "$PREFIX" snk complete -P "$PREFIX" token #<<# } # This function completes authentication classes function completion//bsd::completeauthclass { typeset classes while IFS='|' read -rA classes; do case ${classes[#]} in (0) ;; (1) complete -P "$PREFIX" -- "${classes[1]}" ;; (*) complete -P "$PREFIX" -D "${classes[-1]}" -- "${classes[1,-2]}" ;; esac done <(sed -e ' /^#/ d :b /\\$/ { N bb } s/\\\n//g s/[:].*$// ' /etc/login.conf 2>/dev/null) } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/_dot000066400000000000000000000013401354143602500165400ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "." built-in command. function completion/. { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A --no-alias; disable alias substitution while executing the script" "L --autoload; load script from \$YASH_LOADPATH" "--help" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -le 0 ]; then # complete as a script file name case $TARGETWORD in */*) complete -f ;; * ) complete --external-command ;; esac else # complete an argument to the script command -f completion//reexecute fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/alias000066400000000000000000000006751354143602500167160ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "alias" built-in command. function completion/alias { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "g --global; define global aliases" "p --prefix; print aliases as reusable shell commands" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -a ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/array000066400000000000000000000016771354143602500167460ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "array" built-in command. function completion/array { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "d --delete; remove elements from an array" "i --insert; insert elements to an array" "s --set; replace an element of an array" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset i=1 type= while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i++]} in (-d|--delete) type=d ;; (-i|--insert) type=i ;; (-s|--set ) type=s ;; (--) break ;; esac done if [ $i -gt ${WORDS[#]} ]; then complete --array else case $type in (d) ;; # TODO: complete array index (i|s) if [ $i -eq ${WORDS[#]} ]; then # TODO: complete array index else complete -f fi ;; (*) complete -f ;; esac fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/awk000066400000000000000000000042421354143602500164010ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "awk" command. # Supports POSIX 2008, GNU awk 3.1.8, OpenBSD 4.9, NetBSD 5.0. function completion/awk { case $("${WORDS[1]}" --version <>/dev/null 2>&0) in (*'GNU Awk'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "F: ${long:+--field-separator:}; specify the input field separator" "f: ${long:+--file:}; specify an AWK source file to execute" ) #<# if "${WORDS[1]}" -v foo=bar '' <>/dev/null >&0 2>&1; then OPTIONS=("$OPTIONS" #># "v: ${long:+--assign:}; specify an assignment to be done during startup" ) #<# fi case $type in (GNU) OPTIONS=("$OPTIONS" #># "O --optimize; enable optimization" "W:" "--copyright --copyleft; print copyright info" "--dump-variables::; print variables to the specified file" "--exec:; specify an AWK source file to execute last" "--gen-po; generate PO file" "--lint-old; print warnings about non-portable new constructs" "--lint::; print warnings about dubious or non-portable constructs" "--non-decimal-data; allow octal and hexadecimal numbers" "--posix; allow POSIX-compatible constructs only" "--profile::; write profiling data to the specified file" "--re-interval; allow intervals in regular expressions" "--source:; specify AWK source code to execute" "--traditional --compat; disable GNU extensions" "--use-lc-numeric; use locale's decimal point character" "--help --usage" "--version" ) #<# ;; (OpenBSD|NetBSD) OPTIONS=("$OPTIONS" #># "d::; debug mode" "-safe; disable file output, process creation, and accessing environment variables" "V; print version info" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (d) ;; (F) ;; (v|--assign) ;; (W) #TODO: -W long option ;; (--lint) #>># complete -D "treat warnings as errors" fatal complete -D "warn about invalid constructs only" invalid ;; #<<# (--source) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/basename000066400000000000000000000015751354143602500174000ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "basename" command. # Supports POSIX 2008, GNU coreutils 8.4. function completion/basename { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type= ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=() case $type in (GNU) OPTIONS=("$OPTIONS" "--help" "--version") ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion//getoperands case ${WORDS[#]} in (0) complete -f ;; (1) typeset word="$(sed -e 's;/*$;;' -e 's;^.*/;;' <<<"${WORDS[1]}")" while word=${word#?}; [ "$word" ]; do complete -- "$word" done ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/bash000066400000000000000000000002621354143602500165320ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "bash" command. function completion/bash { command -f completion//reexecute sh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/bg000066400000000000000000000002641354143602500162070ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "bg" built-in command. function completion/bg { command -f completion//reexecute jobs } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/bindkey000066400000000000000000000232671354143602500172540ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "bindkey" built-in command. function completion/bindkey { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a --vi-command; change or print vi-command-mode key bindings" "e --emacs; change or print emacs-mode key bindings" "l --list; print all key binding command names" "v --vi-insert; change or print vi-insert-mode key bindings" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -le 0 ]; then # complete a key sequence TARGETWORD=${TARGETWORD//\\\\/} case $TARGETWORD in (*\\*) PREFIX=${TARGETWORD%\\*} #>># complete -T -P "$PREFIX" '\\' -D "backslash" complete -T -P "$PREFIX" '\B' -D "backspace" complete -T -P "$PREFIX" '\D' -D "down arrow" complete -T -P "$PREFIX" '\E' -D "end" complete -T -P "$PREFIX" '\H' -D "home" complete -T -P "$PREFIX" '\I' -D "insert" complete -T -P "$PREFIX" '\L' -D "left arrow" complete -T -P "$PREFIX" '\N' -D "page-down" complete -T -P "$PREFIX" '\P' -D "page-up" complete -T -P "$PREFIX" '\R' -D "right arrow" complete -T -P "$PREFIX" '\U' -D "up arrow" complete -T -P "$PREFIX" '\X' -D "delete" complete -T -P "$PREFIX" '\!' -D "INTR (normally Ctrl-C)" complete -T -P "$PREFIX" '\#' -D "EOF (normally Ctrl-D)" complete -T -P "$PREFIX" '\$' -D "KILL (normally Ctrl-U)" complete -T -P "$PREFIX" '\?' -D "ERASE (normally Ctrl-H)" complete -T -P "$PREFIX" '\^@' -D "Ctrl-@" complete -T -P "$PREFIX" '\^A' -D "Ctrl-A" complete -T -P "$PREFIX" '\^B' -D "Ctrl-B" complete -T -P "$PREFIX" '\^C' -D "Ctrl-C" complete -T -P "$PREFIX" '\^D' -D "Ctrl-D" complete -T -P "$PREFIX" '\^E' -D "Ctrl-E" complete -T -P "$PREFIX" '\^F' -D "Ctrl-F" complete -T -P "$PREFIX" '\^G' -D "Ctrl-G" complete -T -P "$PREFIX" '\^H' -D "Ctrl-H" complete -T -P "$PREFIX" '\^I' -D "Ctrl-I (tab)" complete -T -P "$PREFIX" '\^J' -D "Ctrl-J (newline)" complete -T -P "$PREFIX" '\^K' -D "Ctrl-K" complete -T -P "$PREFIX" '\^L' -D "Ctrl-L" complete -T -P "$PREFIX" '\^M' -D "Ctrl-M (carriage return)" complete -T -P "$PREFIX" '\^N' -D "Ctrl-N" complete -T -P "$PREFIX" '\^O' -D "Ctrl-O" complete -T -P "$PREFIX" '\^P' -D "Ctrl-P" complete -T -P "$PREFIX" '\^Q' -D "Ctrl-Q" complete -T -P "$PREFIX" '\^R' -D "Ctrl-R" complete -T -P "$PREFIX" '\^S' -D "Ctrl-S" complete -T -P "$PREFIX" '\^T' -D "Ctrl-T" complete -T -P "$PREFIX" '\^U' -D "Ctrl-U" complete -T -P "$PREFIX" '\^V' -D "Ctrl-V" complete -T -P "$PREFIX" '\^W' -D "Ctrl-W" complete -T -P "$PREFIX" '\^X' -D "Ctrl-X" complete -T -P "$PREFIX" '\^Y' -D "Ctrl-Y" complete -T -P "$PREFIX" '\^Z' -D "Ctrl-Z" complete -T -P "$PREFIX" '\^[' -D "Ctrl-[ (escape)" complete -T -P "$PREFIX" '\^\' -D 'Ctrl-\' complete -T -P "$PREFIX" '\^]' -D "Ctrl-]" complete -T -P "$PREFIX" '\^^' -D "Ctrl-^" complete -T -P "$PREFIX" '\^_' -D "Ctrl-_" complete -T -P "$PREFIX" '\^?' -D "Ctrl-?" complete -T -P "$PREFIX" '\F00' -D "F0" complete -T -P "$PREFIX" '\F01' -D "F1" complete -T -P "$PREFIX" '\F02' -D "F2" complete -T -P "$PREFIX" '\F03' -D "F3" complete -T -P "$PREFIX" '\F04' -D "F4" complete -T -P "$PREFIX" '\F05' -D "F5" complete -T -P "$PREFIX" '\F06' -D "F6" complete -T -P "$PREFIX" '\F07' -D "F7" complete -T -P "$PREFIX" '\F08' -D "F8" complete -T -P "$PREFIX" '\F09' -D "F9" complete -T -P "$PREFIX" '\F10' -D "F10" complete -T -P "$PREFIX" '\F11' -D "F11" complete -T -P "$PREFIX" '\F12' -D "F12" complete -T -P "$PREFIX" '\F13' -D "F13" complete -T -P "$PREFIX" '\F14' -D "F14" complete -T -P "$PREFIX" '\F15' -D "F15" complete -T -P "$PREFIX" '\F16' -D "F16" complete -T -P "$PREFIX" '\F17' -D "F17" complete -T -P "$PREFIX" '\F18' -D "F18" complete -T -P "$PREFIX" '\F19' -D "F19" complete -T -P "$PREFIX" '\F20' -D "F20" complete -T -P "$PREFIX" '\F21' -D "F21" complete -T -P "$PREFIX" '\F22' -D "F22" complete -T -P "$PREFIX" '\F23' -D "F23" complete -T -P "$PREFIX" '\F24' -D "F24" complete -T -P "$PREFIX" '\F25' -D "F25" complete -T -P "$PREFIX" '\F26' -D "F26" complete -T -P "$PREFIX" '\F27' -D "F27" complete -T -P "$PREFIX" '\F28' -D "F28" complete -T -P "$PREFIX" '\F29' -D "F29" complete -T -P "$PREFIX" '\F30' -D "F30" complete -T -P "$PREFIX" '\F31' -D "F31" complete -T -P "$PREFIX" '\F32' -D "F32" complete -T -P "$PREFIX" '\F33' -D "F33" complete -T -P "$PREFIX" '\F34' -D "F34" complete -T -P "$PREFIX" '\F35' -D "F35" complete -T -P "$PREFIX" '\F36' -D "F36" complete -T -P "$PREFIX" '\F37' -D "F37" complete -T -P "$PREFIX" '\F38' -D "F38" complete -T -P "$PREFIX" '\F39' -D "F39" complete -T -P "$PREFIX" '\F40' -D "F40" complete -T -P "$PREFIX" '\F41' -D "F41" complete -T -P "$PREFIX" '\F42' -D "F42" complete -T -P "$PREFIX" '\F43' -D "F43" complete -T -P "$PREFIX" '\F44' -D "F44" complete -T -P "$PREFIX" '\F45' -D "F45" complete -T -P "$PREFIX" '\F46' -D "F46" complete -T -P "$PREFIX" '\F47' -D "F47" complete -T -P "$PREFIX" '\F48' -D "F48" complete -T -P "$PREFIX" '\F49' -D "F49" complete -T -P "$PREFIX" '\F50' -D "F50" complete -T -P "$PREFIX" '\F51' -D "F51" complete -T -P "$PREFIX" '\F52' -D "F52" complete -T -P "$PREFIX" '\F53' -D "F53" complete -T -P "$PREFIX" '\F54' -D "F54" complete -T -P "$PREFIX" '\F55' -D "F55" complete -T -P "$PREFIX" '\F56' -D "F56" complete -T -P "$PREFIX" '\F57' -D "F57" complete -T -P "$PREFIX" '\F58' -D "F58" complete -T -P "$PREFIX" '\F59' -D "F59" complete -T -P "$PREFIX" '\F60' -D "F60" complete -T -P "$PREFIX" '\F61' -D "F61" complete -T -P "$PREFIX" '\F62' -D "F62" complete -T -P "$PREFIX" '\F63' -D "F63" complete -T -P "$PREFIX" '\a1' -D "keypad upper-left" complete -T -P "$PREFIX" '\a3' -D "keypad upper-right" complete -T -P "$PREFIX" '\b2' -D "keypad center" complete -T -P "$PREFIX" '\c1' -D "keypad lower-left" complete -T -P "$PREFIX" '\c3' -D "keypad lower-right" complete -T -P "$PREFIX" '\ca' -D "clear all tabs" complete -T -P "$PREFIX" '\cl' -D "close" complete -T -P "$PREFIX" '\cn' -D "cancel" complete -T -P "$PREFIX" '\co' -D "command" complete -T -P "$PREFIX" '\cp' -D "copy" complete -T -P "$PREFIX" '\cr' -D "create" complete -T -P "$PREFIX" '\cs' -D "clear screen" complete -T -P "$PREFIX" '\ct' -D "clear tab" complete -T -P "$PREFIX" '\dl' -D "delete line" complete -T -P "$PREFIX" '\ei' -D "exit insert mode" complete -T -P "$PREFIX" '\el' -D "clear to end of line" complete -T -P "$PREFIX" '\es' -D "clear to end of screen" complete -T -P "$PREFIX" '\et' -D "enter (send)" complete -T -P "$PREFIX" '\fd' -D "find" complete -T -P "$PREFIX" '\hp' -D "help" complete -T -P "$PREFIX" '\il' -D "insert line" complete -T -P "$PREFIX" '\ll' -D "home down" complete -T -P "$PREFIX" '\me' -D "message" complete -T -P "$PREFIX" '\mk' -D "mark" complete -T -P "$PREFIX" '\ms' -D "mouse event" complete -T -P "$PREFIX" '\mv' -D "move" complete -T -P "$PREFIX" '\nx' -D "next object" complete -T -P "$PREFIX" '\on' -D "open" complete -T -P "$PREFIX" '\op' -D "options" complete -T -P "$PREFIX" '\pr' -D "print (copy)" complete -T -P "$PREFIX" '\pv' -D "previous object" complete -T -P "$PREFIX" '\rd' -D "redo" complete -T -P "$PREFIX" '\re' -D "resume" complete -T -P "$PREFIX" '\rf' -D "reference" complete -T -P "$PREFIX" '\rh' -D "refresh" complete -T -P "$PREFIX" '\rp' -D "replace" complete -T -P "$PREFIX" '\rs' -D "restart" complete -T -P "$PREFIX" '\sf' -D "scroll forward" complete -T -P "$PREFIX" '\sl' -D "select" complete -T -P "$PREFIX" '\sr' -D "scroll backward" complete -T -P "$PREFIX" '\st' -D "set tab" complete -T -P "$PREFIX" '\su' -D "suspend" complete -T -P "$PREFIX" '\sv' -D "save" complete -T -P "$PREFIX" '\ud' -D "undo" complete -T -P "$PREFIX" '\SE' -D "shift + end" complete -T -P "$PREFIX" '\SH' -D "shift + home" complete -T -P "$PREFIX" '\SI' -D "shift + insert" complete -T -P "$PREFIX" '\SL' -D "shift + left arrow" complete -T -P "$PREFIX" '\SR' -D "shift + right arrow" complete -T -P "$PREFIX" '\SX' -D "shift + delete" complete -T -P "$PREFIX" '\Sbg' -D "shift + beginning" complete -T -P "$PREFIX" '\Scn' -D "shift + cancel" complete -T -P "$PREFIX" '\Sco' -D "shift + command" complete -T -P "$PREFIX" '\Scp' -D "shift + copy" complete -T -P "$PREFIX" '\Scr' -D "shift + create" complete -T -P "$PREFIX" '\Sdl' -D "shift + delete line" complete -T -P "$PREFIX" '\Sel' -D "shift + end of line" complete -T -P "$PREFIX" '\Sex' -D "shift + exit" complete -T -P "$PREFIX" '\Sfd' -D "shift + find" complete -T -P "$PREFIX" '\Shp' -D "shift + help" complete -T -P "$PREFIX" '\Smg' -D "shift + message" complete -T -P "$PREFIX" '\Smv' -D "shift + move" complete -T -P "$PREFIX" '\Snx' -D "shift + next" complete -T -P "$PREFIX" '\Sop' -D "shift + options" complete -T -P "$PREFIX" '\Spr' -D "shift + print" complete -T -P "$PREFIX" '\Spv' -D "shift + previous" complete -T -P "$PREFIX" '\Srd' -D "shift + redo" complete -T -P "$PREFIX" '\Sre' -D "shift + resume" complete -T -P "$PREFIX" '\Srp' -D "shift + replace" complete -T -P "$PREFIX" '\Ssu' -D "shift + suspend" complete -T -P "$PREFIX" '\Ssv' -D "shift + save" complete -T -P "$PREFIX" '\Sud' -D "shift + undo" #<<# esac else complete --bindkey -- - fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/break000066400000000000000000000005641354143602500167060ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "break" built-in command. function completion/break { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "i --iteration; break iterative execution" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/bsdtar000066400000000000000000000002671354143602500171010ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "bsdtar" command. function completion/bsdtar { command -f completion//reexecute tar } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/carthage000066400000000000000000000106131354143602500173740ustar00rootroot00000000000000# (C) 2016-2017 magicant # Completion script for the "carthage" command. # Supports Carthage 0.20. function completion/carthage { typeset OPTIONS ARGOPT PREFIX OPTIONS=() case ${WORDS[#]} in (1) command -f completion/carthage::completesubcmds ;; (*) typeset subcmd="${WORDS[2]}" if command -vf "completion/carthage::$subcmd:arg" >/dev/null 2>&1; then WORDS=("${WORDS[2,-1]}") command -f "completion/carthage::$subcmd:arg" fi ;; esac } function completion/carthage::getbuildoptions { OPTIONS=("$OPTIONS" #># "--cache-builds; reuse cached builds" "--configuration:" "--derived-data:" "--platform:" "--toolchain:" "--verbose" ) #<# } function completion/carthage::getcheckoutoptions { OPTIONS=("$OPTIONS" #># "--no-use-binaries" "--use-ssh" "--use-submodules" ) #<# } function completion/carthage::getcpoptions { OPTIONS=("$OPTIONS" #># "--color:" "--project-directory:" ) #<# } function completion/carthage::completeoptarg case $ARGOPT in (-) command -f completion//completeoptions -e ;; (--derived-data|--project-directory) complete -P "$PREFIX" -S / -T -d ;; (--color) complete -P "$PREFIX" always auto never ;; (--configuration) # TODO Complete configuration ;; (--platform) typeset word="${TARGETWORD#"$PREFIX"}" word=${word##*,} PREFIX=${TARGETWORD%"$word"} complete -P "$PREFIX" all macOS iOS watchOS tvOS ;; (--toolchain) # TODO Complete toolchain ;; esac function completion/carthage::completesubcmds { #>># complete -P "${PREFIX-}" archive bootstrap build checkout fetch help \ outdated update version } #<<# function completion/carthage::completedependencies { typeset saveopts="$(set +o)" set +o glob # This code can only complete dependencies that have already been # checked out in Carthage/Checkouts. Maybe we could improve candidates # by parsing Cartfile. typeset IFS=/ complete -P "$PREFIX" -- $( set -o glob -o nullglob exec 2>/dev/null typeset CDPATH= cd -P "./$(git rev-parse --show-cdup)Carthage/Checkouts" || exit candidates=(*/) candidates=("${candidates%/}") printf %s "${candidates[*]}" ) eval "$saveopts" } function completion/carthage::archive:arg { OPTIONS=( #># "--output:" ) #<# command -f completion/carthage::getcpoptions command -f completion//parseoptions -e case $ARGOPT in ('') # TODO complete framework ;; (--output) complete -P "$PREFIX" -f ;; (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::bootstrap:arg { OPTIONS=( #># "--no-build" "--no-checkout" ) #<# command -f completion/carthage::getbuildoptions command -f completion/carthage::getcheckoutoptions command -f completion/carthage::getcpoptions command -f completion//parseoptions -e case $ARGOPT in ('') command -f completion/carthage::completedependencies ;; (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::build:arg { OPTIONS=( #># "--no-skip-current; build current project as well" ) #<# command -f completion/carthage::getbuildoptions command -f completion/carthage::getcpoptions command -f completion//parseoptions -e case $ARGOPT in ('') command -f completion/carthage::completedependencies ;; (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::checkout:arg { OPTIONS=() command -f completion/carthage::getcheckoutoptions command -f completion/carthage::getcpoptions command -f completion//parseoptions -e case $ARGOPT in ('') command -f completion/carthage::completedependencies ;; (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::fetch:arg { OPTIONS=( #># "--color:" ) #<# command -f completion//parseoptions -e case $ARGOPT in ('') # TODO complete repository ;; (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::help:arg { command -f completion/carthage::completesubcmds } function completion/carthage::outdated:arg { OPTIONS=( #># "--use-ssh" "--verbose" ) #<# command -f completion/carthage::getcpoptions command -f completion//parseoptions -e case $ARGOPT in (*) command -f completion/carthage::completeoptarg ;; esac } function completion/carthage::update:arg { command -f completion/carthage::bootstrap:arg "$@" } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/cat000066400000000000000000000047171354143602500163750ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "cat" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/cat { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "u; disable buffering" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS|HP-UX) ADDOPTIONS=( #># "b ${long:+--number-nonblank}; print line number for each non-empty line" "n ${long:+--number}; print line number for each line" "v ${long:+--show-nonprinting}; print unprintable characters like ^I or M-C" ) #<# case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "A --show-all; make everything visible" "E --show-ends; print \$ at end of each line" "e; like -vE: make everything visible but tabs" "T --show-tabs; print tabs as ^I" "t; like -vT: make everything visible but newlines" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "e; print \$ at end of each line (with -v)" "t; print tabs and form-feeds as ^I and ^L (with -v)" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "e; like -v and print \$ at end of each line" "t; like -v and print tabs and form-feeds as ^I and ^L" ) #<# ;; (*) ADDOPTIONS=("$ADDOPTIONS" #># "e; like -v and print \$ at end of each line" "t; like -v and print tabs as ^I" ) #<# ;; esac case $type in (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "s; suppress error messages" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "r; squeeze adjacent empty lines into one" "s; suppress error messages" ) #<# ;; (*) ADDOPTIONS=("$ADDOPTIONS" #># "s; squeeze adjacent empty lines into one" ) #<# ;; esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "--help" "--version" ) #<# ;; (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "f; print regular files only" "l; acquire lock on the standard output while printing" ) #<# ;; esac esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/cd000066400000000000000000000053161354143602500162100ustar00rootroot00000000000000# (C) 2010-2018 magicant # Completion script for the "cd" built-in command. # Completion function "completion/cd" is used for the "dirs", "pushd", "popd" # built-ins as well. function completion/cd { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# case ${WORDS[1]} in (cd|pushd) OPTIONS=("$OPTIONS" #># "L --logical; keep symbolic links in the \$PWD variable as is" "P --physical; resolve symbolic links in the \$PWD variable" "--default-directory:; specify the default directory to change to" ) #<# if [ "${WORDS[1]}" = "pushd" ]; then OPTIONS=("$OPTIONS" #># "--remove-duplicates; remove duplicates in the directory stack" ) #<# fi ;; (dirs) OPTIONS=("$OPTIONS" #># "c --clear; clear the directory stack completely" "v --verbose; print stack entries with their indices" ) #<# ;; esac typeset dirstack=false command -f completion//parseoptions -es case $ARGOPT in (-) case $TARGETWORD in (-*[[:digit:]]*) ;; (*) command -f completion//completeoptions ;; esac dirstack=true ;; (--default-directory) command -f completion/cd::completedir ;; (*) case ${WORDS[1]} in (cd|pushd) command -f completion/cd::completedir esac dirstack=true ;; esac if $dirstack; then case ${WORDS[1]} in (dirs|pushd|popd) complete -P "$PREFIX" --dirstack-index esac fi } function completion/cd::completedir { # Part 1: complete directories relative to $PWD # -L or -P? typeset logical=true option for option in "${WORDS[2,-1]}"; do case $option in (-L|--logical) logical=true ;; (-P|--physical) logical=false ;; (--) break ;; esac done typeset saveopts="$(set +o)" set +o glob typeset word="${TARGETWORD#"$PREFIX"}" typeset basename="${word##*/}" typeset dirname="${word%"$basename"}" if $logical; then typeset IFS=/ complete -T -P "$PREFIX$dirname" -S / -- $( set -o errreturn -o glob -o nullglob typeset CDPATH= if [ "$dirname" ]; then command cd -L -- "$dirname" >/dev/null 2>&1 fi candidates=("$basename"*/) candidates=("${candidates%/}") printf %s "${candidates[*]}" ) else complete -T -P "$PREFIX" -S / -d fi # Part 2: complete directories relative to $CDPATH case $word in (/*|./*|../*) eval "$saveopts" return esac set -o glob -o nullglob typeset IFS=: cdpath candidates for cdpath in ${CDPATH-}; do if [ "$cdpath" = "" ]; then cdpath=. fi # list candidates by globbing candidates=("${cdpath%/}/$word"*/) # remove $cdpath and trailing slash candidates=("${candidates%/}") candidates=("${candidates##"${cdpath%/}/"}") complete -T -P "$PREFIX$dirname" -S / \ -- "${candidates#"$dirname"}" done eval "$saveopts" } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/chgrp000066400000000000000000000041151354143602500167210ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "chgrp" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/chgrp { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "h ${long:+--no-dereference}; change the group of symbolic links" "H; follow symbolic links to directories in operands (with -R)" "L; follow all symbolic links to directories (with -R)" "P; don't follow any symbolic links (with -R)" "R ${long:+--recursive}; recursively change the group of files in a directory" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "f ${long:+--silent --quiet}; suppress error messages" ) #<# case $type in (GNU|NetBSD|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "c --changes; print a message when a change has been made" "--dereference; change the group of files referred to by symbolic links" "--no-preserve-root; cancel the --preserve-root option" "--preserve-root; don't recursively change the group of the root directory" "--reference:; specify a file to whose group changes are made" "--help" "--version" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "x; stop recursion when reaching a different file system" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--reference) complete -P "$PREFIX" -f ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then complete -g else complete -f fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/chmod000066400000000000000000000076201354143602500167140ustar00rootroot00000000000000# (C) 2010-2013 magicant # Completion script for the "chmod" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/chmod { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "R ${long:+--recursive}; recursively change the permission of files in a directory" ) #<# ADDOPTIONS=() case $type in (GNU|FreeBSD|NetBSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "f ${long:+--silent --quiet}; ignore errors" ) #<# case $type in (GNU|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# esac case $type in (FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "h; change the mode of symbolic links" ) #<# esac esac case $type in (*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "H; follow symbolic links in operands (with -R)" "L; follow all symbolic links (with -R)" "P; don't follow symbolic links (with -R)" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "c --changes; print a message when a change has been made" "--no-preserve-root; cancel the --preserve-root option" "--preserve-root; don't recursively change the permission of the root directory" "--reference:; specify a file to whose permission changes are made" "--help" "--version" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "A; preserve access control list entries" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--reference) complete -P "$PREFIX" -f ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then command -f completion/chmod::mode chmod else complete -f fi ;; esac } function completion/chmod::mode { typeset word="${TARGETWORD#"$PREFIX"}" # we don't complete a numeric mode value case $word in (*[[:digit:]]*) return esac word=${word##*,} case $word in (*[-+=]*) case $word in (*[-+=]*[ugo]*) return ;; (*[-+=]) #>># complete -P "$TARGETWORD" -D "copy from the user part of permission" u complete -P "$TARGETWORD" -D "copy from the group part of permission" g complete -P "$TARGETWORD" -D "copy from the other part of permission" o ;; #<<# esac #>># complete -P "$TARGETWORD" -D "read permission" r complete -P "$TARGETWORD" -D "write permission" w complete -P "$TARGETWORD" -D "execute permission" x complete -P "$TARGETWORD" -D "execute permission (only when there's already one)" X #<<# case ${1-} in (chmod|find|mkdir|tar|umask) #>># complete -P "$TARGETWORD" -D "set-user/group-ID" s esac #<<# case ${1-} in (chmod|find|mkdir|tar) #>># complete -P "$TARGETWORD" -D "sticky bit" t esac #<<# return esac case $word in ('') case ${1-} in (rsync) #>># complete -T -P "$TARGETWORD" -D "affect directories only" D complete -T -P "$TARGETWORD" -D "affect non-directories only" F esac #<<# esac case $word in (*a*) ;; (*) #>># complete -T -P "$TARGETWORD" -D "affect the user part of the permission" u complete -T -P "$TARGETWORD" -D "affect the group part of the permission" g complete -T -P "$TARGETWORD" -D "affect the other part of the permission" o complete -T -P "$TARGETWORD" -D "affect all parts of the permission" a ;; #<<# esac #>># complete -T -P "$TARGETWORD" -D "add the specified permission" -- + complete -T -P "$TARGETWORD" -D "remove the specified permission" -- - complete -T -P "$TARGETWORD" -D "set the permission to the specified one" -- = #<<# } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/chown000066400000000000000000000046371354143602500167450ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "chown" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/chown { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "h ${long:+--no-dereference}; change the owner of symbolic links" "H; follow symbolic links to directories in operands (with -R)" "L; follow all symbolic links to directories (with -R)" "P; don't follow any symbolic links (with -R)" "R ${long:+--recursive}; recursively change the owner of files in a directory" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "f ${long:+--silent --quiet}; suppress error messages" ) #<# case $type in (GNU|NetBSD|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "c --changes; print a message when a change has been made" "--dereference; change the owner of files referred to by symbolic links" "--from:; specify an owner:group only from which changes are made" "--no-preserve-root; cancel the --preserve-root option" "--preserve-root; don't recursively change the owner of the root directory" "--reference:; specify a file to whose owner changes are made" "--help" "--version" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "x; stop recursion when reaching a different file system" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS typeset ownergroup=false command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--reference) complete -P "$PREFIX" -f ;; (--from) ownergroup=true ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then ownergroup=true else complete -f fi ;; esac if $ownergroup; then case ${TARGETWORD#"$PREFIX"} in (*:*) PREFIX=$PREFIX${${TARGETWORD#"$PREFIX"}%%:*}: complete -P "$PREFIX" -g ;; (*) complete -T -P "$PREFIX" -S : -u ;; esac fi } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/chsh000066400000000000000000000021261354143602500165430ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "chsh" command. # Supports util-linux 2.18, FreeBSD 8.1, OpenBSD 4.9, NetBSD 5.0, # Mac OS X 10.6.4, HP-UX 11i v3. function completion/chsh { case $(uname 2>/dev/null) in (Linux) typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "s: --shell:; specify the new shell" "l --list-shells; print available shells" "u --help; print help" "v --version; print version into" ) #<# ;; (*BSD|Darwin) command -f completion//reexecute chpass return ;; (HP-UX) typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "r:; specify the repository to operate on" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (r) complete -P "$PREFIX" dce files nis ;; (s|--shell) complete -P "$PREFIX" -- $(grep -v ^# /etc/shells 2>/dev/null) ;; (*) command -f completion//getoperands case ${WORDS[#]} in (0) complete -u ;; (*) complete -P "$PREFIX" -- $(grep -v ^# /etc/shells 2>/dev/null) ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/cmp000066400000000000000000000027221354143602500163770ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "cmp" command. # Supports POSIX 2008, GNU diffutils 3.0, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # SunOS 5.10, HP-UX 11i v3. function completion/cmp { case $("${WORDS[1]}" --version 2>/dev/null) in (*'diffutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "l ${long:+--verbose}; print all differing byte values" "s ${long:+--silent --quiet}; print nothing" ) #<# ADDOPTIONS=() case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "b --print-bytes; print differing byte values in ^-notation" "i: --ignore-initial:; specify number of bytes to skip" "n: --bytes:; specify number of bytes to compare at most" "--help" "v --version; print version info" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "h; don't follow symbolic links" "x; print all differing byte values in hexadecimal" "z; firstly compare by size" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (i|--ignore-initial) ;; (n|--bytes) ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -lt 2 ]; then complete -f fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/comm000066400000000000000000000026751354143602500165620ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "comm" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/comm { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "1; don't print column of lines unique to file 1" "2; don't print column of lines unique to file 2" "3; don't print column of lines common to both files" ) #<# ADDOPTIONS=() case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "--check-order; check if files are correctly sorted" "--nocheck-order; don't check if files are correctly sorted" "--output-delimiter:; specify a delimiter to separate columns" "--help" "--version" ) #<# ;; (OpenBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "f; case-insensitive comparison" ) #<# ;; (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "i; case-insensitive comparison" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--output-delimiter) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/command000066400000000000000000000046721354143602500172440ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "command" built-in command. # Completion function "completion/command" is used for the "type" built-in as # well. function completion/command { typeset OPTIONS ARGOPT PREFIX typeset COMMONOPTIONS VOPTIONS NOVOPTIONS COMMONOPTIONS=( #># "b --builtin-command; execute or find a built-in command" "e --external-command; execute or find an external command" "f --function; execute or find a function" "p --standard-path; use the standard path in searching for a command" "--help" ) #<# VOPTIONS=( #># "a --alias; find an alias" "k --keyword; find a shell keyword" ) #<# NOVOPTIONS=( #># "v --identify; print the full path of a command" "V --verbose-identify; describe how a command is interpreted" ) #<# OPTIONS=("$COMMONOPTIONS" "$VOPTIONS" "$NOVOPTIONS") command -f completion//parseoptions typeset i=2 options= while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i++]} in (--) break ;; (--help) return ;; (-a|--alias) options=${options}a ;; (-b|--builtin-command) options=${options}b ;; (-e|--external-command) options=${options}e ;; (-f|--function) options=${options}f ;; (-k|--keyword) options=${options}k ;; (-p|--standard-path) options=${options}p ;; (-v|--identify) options=${options}v ;; (-V|--verbose-identify) options=${options}V ;; esac done WORDS=("${WORDS[i,-1]}") case $ARGOPT in (-) case $options in (*[vV]*) OPTIONS=("$COMMONOPTIONS" "$VOPTIONS") ;; (*) OPTIONS=("$COMMONOPTIONS" "$NOVOPTIONS") ;; esac command -f completion//completeoptions ;; (*) if case $options in (*[vV]*) if [ -z "${options//[pvV]}" ]; then options=${options}abefk fi true ;; (*) if [ -z "${options//p}" ]; then options=${options}be fi [ ${WORDS[#]} -eq 0 ] ;; esac then # complete command name case $options in (*a*) complete --alias esac case $options in (*b*) complete --builtin-command esac case $options in (*e*) complete -T -S / -d case $TARGETWORD in (*/*) complete --executable-file ;; (* ) complete --external-command ;; esac esac case $options in (*f*) complete --function esac case $options in (*k*) complete --keyword esac else # complete command argument command -f completion//reexecute fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/complete000066400000000000000000000005161354143602500174270ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "complete" built-in command. function completion/complete { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/configure000066400000000000000000000012731354143602500176010ustar00rootroot00000000000000# (C) 2012 magicant # Completion script for the "configure" script. function completion/configure { case $TARGETWORD in (*=*) complete -P "${TARGETWORD%%=*}=" -f return ;; (-*) ;; (*) return ;; esac typeset words option desc i IFS=' ' while read -A words; do i=1 while true; do case ${words[i]} in (-*) i=$((i+1)) ;; (*) break ;; esac done desc=${words[i,-1]} for option in "${words[1,i-1]%,}"; do case $option in (--*=*) complete -OT -D "$desc" -- "${option%=*}=" ;; (-*) complete -O -D "$desc" -- "${option}" ;; esac done done <("${WORDS[1]}" --help 2>&1) } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/continue000066400000000000000000000005751354143602500174500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "continue" built-in command. function completion/continue { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "i --iteration; continue iterative execution" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/cp000066400000000000000000000116521354143602500162240ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "cp" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/cp { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "f ${long:+--force}; remove unwritable destination files before copying" "H; follow symbolic links in source operands" "i ${long:+--interactive}; ask before overwriting existing files" "L ${long:+--dereference}; follow all symbolic links in source files" "P ${long:+--no-dereference}; copy symbolic links as symbolic links" "R ${long:+--recursive}; recursively copy directories" ) #<# ADDOPTIONS=() case $type in (GNU|FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# case $type in (GNU|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "n ${long:+--no-clobber}; don't overwrite existing files" "x ${long:+--one-file-system}; skip subdirectories on different file systems" ) #<# case $type in (GNU|FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "l ${long:+--link}; make hard links instead of copying" ) #<# esac case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "a; like -PpR" ) #<# esac esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "a --archive; like -PR --preserve=all" "b; like --backup=existing" "--backup::; specify how to make a backup" "c; like --preserve=context" "--copy-contents; treat special files as regular files in recursion" "d; like -P --preserve=links" "p; like --preserve=mode,ownership,timestamps" "--preserve::; specify file attributes to preserve" "--no-preserve:; specify file attributes not to preserve" "--parents; mirror parent directories in source to destination" "--reflink::; lightweight (copy-on-write) copy" "--remove-destination; remove existing destination files before copying" "--sparse:; specify when to make sparse copies" "--strip-trailing-slashes; remove trailing slashes from source operands" "s --symbolic-link; make symbolic links instead of copying" "S: --suffix:; specify a suffix to append to backup file names" "t: --target-directory:; specify a destination directory" "T --no-target-directory; always treat the destination as a regular file" "u --update; don't copy if destination is newer than source" "Z: --context:; specify security context of copies" "--help" "--version" ) #<# ;; (*BSD|Darwin|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "p; preserve file attributes" ) #<# case $type in (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "N; don't copy file flags (with -p)" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "@; preserve extended attributes" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "e:; specify extent attributes treatment" "S; safe mode" ) #<# ;; esac ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--backup) if command -vf completion//completebackup >/dev/null 2>&1 || . -AL completion/_backup; then command -f completion//completebackup fi ;; (e) #>># complete -P "$PREFIX" -D "print a message when failed to copy an extent attribute" warn complete -P "$PREFIX" -D "don't copy extent attributes" ignore complete -P "$PREFIX" -D "don't copy a file when cannot copy the extent attribute" force ;; #<<# (--preserve|--no-preserve) typeset word word=${TARGETWORD#"$PREFIX"} word=${word##*,} PREFIX=${TARGETWORD%"$word"} #>># complete -T -P "$PREFIX" -S , -D "file permission bits" mode complete -T -P "$PREFIX" -S , -D "owner and group" ownership complete -T -P "$PREFIX" -S , -D "last access/modification time" timestamps complete -T -P "$PREFIX" -S , -D "hard links" links complete -T -P "$PREFIX" -S , -D "SELinux security context" context complete -T -P "$PREFIX" -S , -D "extended attributes" xattr complete -P "$PREFIX" -D "all attributes" all ;; #<<# (--reflink) #>># complete -P "$PREFIX" -D "print a message when failed to do copy-on-write copy" always complete -P "$PREFIX" -D "fall back to normal copy when failed to do copy-on-write copy" auto ;; #<<# (--sparse) #>># complete -P "$PREFIX" -D "make destination sparse if source is sparse" auto complete -P "$PREFIX" -D "always try to make sparse copies" always complete -P "$PREFIX" -D "never make sparse copies" never ;; #<<# (S|--suffix) ;; (t|--target-directory) complete -T -P "$PREFIX" -S / -d ;; (Z|--context) # TODO ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/crontab000066400000000000000000000021641354143602500172500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "crontab" command. # Supports POSIX 2008, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, Mac OS X 10.6.3, # SunOS 5.10, HP-UX 11i v3. function completion/crontab { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=() case $type in (SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "e:; edit crontab with an editor" "l:; print the current crontab setting" "r:; remove crontab setting" ) #<# ;; (*) OPTIONS=("$OPTIONS" #># "e; edit crontab with an editor" "l; print the current crontab setting" "r; remove crontab setting" "u:; specify a user whose crontab is set, removed, or shown" ) #<# # We should check if crontab supports the -u option, but # there's no reliable way to do so. We just always complete the # -u option. ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (?) complete -u ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/csplit000066400000000000000000000027651354143602500171250ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "csplit" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/csplit { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f: ${long:+--prefix:}; specify the prefix of the output files" "k ${long:+--keep-files}; leave output files intact on an error" "n: ${long:+--digits:}; specify the suffix length for output files" "s ${long:+q --silent --quiet}; don't print output file sizes" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "b: --suffix-format:; specify the format of output file suffix" "z --elide-empty-files; remove empty output files" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (f) complete -P "$PREFIX" -f ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then complete -f else #>># complete -T -D "split before a line matching the following regular expression" / complete -T -D "like / but don't include the matching line" % complete -T -D "repeat the previous operand's behavior the specified times" { fi #<<# ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/cut000066400000000000000000000025551354143602500164170ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "cut" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/cut { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "b: ${long:+--bytes:}; specify the positions of bytes to print" "c: ${long:+--characters:}; specify the positions of characters to print" "d: ${long:+--delimiter:}; specify a field delimiter (with -f)" "f: ${long:+--fields:}; specify the positions of fields to print" "n; don't split multibyte characters (with -b)" "s ${long:+--only-delimited}; don't print lines containing no delimiters (with -f)" ) #<# ADDOPTIONS=() case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "--complement; print fields not specified by -b/-c/-f option" "--help" "--version" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (?|--?*) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/dash000066400000000000000000000002621354143602500165340ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "dash" command. function completion/dash { command -f completion//reexecute sh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/date000066400000000000000000000227011354143602500165340ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "date" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/date { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "u ${long:+--utc --universal}; use Coordinated Universal Time (UTC)" ) #<# ADDOPTIONS=() case $type in (*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "j; don't change the system time" "n; change the system time on the current host only" "r:; specify seconds since the epoch to print" ) #<# case $type in (OpenBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "a; gradually change the system time using the adjtime syscall" ) #<# esac case $type in (OpenBSD|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "d:; specify the new daylight saving time" "t:; specify the new difference from UTC in minutes" ) #<# case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "f:; specify the format of the operand parsed" "v:; specify adjustment for the date and time to print" ) #<# esac esac esac case $type in (GNU|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "d: ${long:+--date:}; specify a date and time to print instead of now" ) #<# esac case $type in (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "a:; specify adjustment for the system clock" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "f: --file:; specify a file containing date/time data to print" "R --rfc-2822; print in the RFC 2822 format" "r: --reference:; specify a file whose last modified time is printed" "--rfc-3339:; specify the type of the RFC 3339 format to print in" "s: --set:; specify a date and time to set the system time to" "--help" "--version" ) #<# esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (f|--file|r|--reference) case $type in (GNU) complete -P "$PREFIX" -f esac ;; (--rfc-3339) #>># complete -P "$PREFIX" -D "e.g. 1999-12-31" date complete -P "$PREFIX" -D "e.g. 1999-12-31 13:45:56+05:30" seconds complete -P "$PREFIX" -D "e.g. 1999-12-31 13:45:56.123456789+05:30" ns ;; #<<# (v) typeset word="${TARGETWORD#"$PREFIX"}" word=${word#[+-]} case $word in ([[:digit:]]*) while [ "${word#[[:digit:]]}" != "$word" ]; do word=${word#[[:digit:]]} done PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" -D "year" y complete -P "$PREFIX" -D "month" m complete -P "$PREFIX" -D "day of week" w complete -P "$PREFIX" -D "day of month" d complete -P "$PREFIX" -D "hour" H complete -P "$PREFIX" -D "minute" M complete -P "$PREFIX" -D "second" S ;; #<<# (*) PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" -D "Sunday" Sunday complete -P "$PREFIX" -D "Monday" Monday complete -P "$PREFIX" -D "Tuesday" Tuesday complete -P "$PREFIX" -D "Wednesday" Wednesday complete -P "$PREFIX" -D "Thursday" Thursday complete -P "$PREFIX" -D "Friday" Friday complete -P "$PREFIX" -D "Saturday" Saturday complete -P "$PREFIX" -D "January" January complete -P "$PREFIX" -D "February" February complete -P "$PREFIX" -D "March" March complete -P "$PREFIX" -D "April" April complete -P "$PREFIX" -D "May" May complete -P "$PREFIX" -D "June" June complete -P "$PREFIX" -D "July" July complete -P "$PREFIX" -D "August" August complete -P "$PREFIX" -D "September" September complete -P "$PREFIX" -D "October" October complete -P "$PREFIX" -D "November" November complete -P "$PREFIX" -D "December" December ;; #<<# esac ;; (?|--?*) ;; (*) command -f completion//completestrftime ;; esac } function completion//completestrftime { typeset word="$TARGETWORD" word=${word#"$PREFIX"} word=${word//%%} case $word in (*%|*%[EO:]|*%::|*%:::) word=%${word##*%} PREFIX=${TARGETWORD%"$word"} #>># complete -T -P "$PREFIX" -D "day of week, full, localized" '%A' complete -T -P "$PREFIX" -D "day of week, short, localized" '%a' complete -T -P "$PREFIX" -D "month, full, localized" '%B' complete -T -P "$PREFIX" -D "month, short, localized" '%b' complete -T -P "$PREFIX" -D "century in numeral" '%C' complete -T -P "$PREFIX" -D "date and time, localized" '%c' complete -T -P "$PREFIX" -D "like %m/%d/%y (e.g. 12/31/99)" '%D' complete -T -P "$PREFIX" -D "day of month [01-31]" '%d' complete -T -P "$PREFIX" -D "day of month [ 1-31] (space-padded)" '%e' complete -T -P "$PREFIX" -D "hour [00-23]" '%H' complete -T -P "$PREFIX" -D "hour [01-12]" '%I' complete -T -P "$PREFIX" -D "day of year [001-366]" '%j' complete -T -P "$PREFIX" -D "minute [00-59]" '%M' complete -T -P "$PREFIX" -D "month [01-12]" '%m' complete -T -P "$PREFIX" -D "newline" '%n' complete -T -P "$PREFIX" -D "AM or PM, localized" '%p' complete -T -P "$PREFIX" -D "12-hour clock time with AM/PM, localized" '%r' complete -T -P "$PREFIX" -D "second [00-60]" '%S' complete -T -P "$PREFIX" -D "like %H:%M:%S (e.g. 13:45:56)" '%T' complete -T -P "$PREFIX" -D "tab" '%t' complete -T -P "$PREFIX" -D "week of year [00-53] (first Sunday in week 1)" '%U' complete -T -P "$PREFIX" -D "day of week in numeral [1-7]" '%u' complete -T -P "$PREFIX" -D "week of year [01-53] (first Monday in week 1 or 2)" '%V' complete -T -P "$PREFIX" -D "week of year [00-53] (first Monday in week 1)" '%W' complete -T -P "$PREFIX" -D "day of week in numeral [0-6]" '%w' complete -T -P "$PREFIX" -D "time, localized" '%X' complete -T -P "$PREFIX" -D "date, localized" '%x' complete -T -P "$PREFIX" -D "year, full (e.g. 1999)" '%Y' complete -T -P "$PREFIX" -D "year within century [00-99]" '%y' complete -T -P "$PREFIX" -D "timezone name" '%Z' complete -T -P "$PREFIX" -D "name of era, localized, alternative format" '%EC' complete -T -P "$PREFIX" -D "date and time, localized, alternative format" '%Ec' complete -T -P "$PREFIX" -D "time, localized, alternative format" '%EX' complete -T -P "$PREFIX" -D "date, localized, alternative format" '%Ex' complete -T -P "$PREFIX" -D "year, full, alternative format" '%EY' complete -T -P "$PREFIX" -D "year within era in numeral, alternative format" '%Ey' complete -T -P "$PREFIX" -D "day of month in localized alternative numeral" '%Od' complete -T -P "$PREFIX" -D "hour in localized alternative numeral, 24-hour clock" '%OH' complete -T -P "$PREFIX" -D "hour in localized alternative numeral, 12-hour clock" '%OI' complete -T -P "$PREFIX" -D "minute in localized alternative numeral" '%OM' complete -T -P "$PREFIX" -D "month in localized alternative numeral" '%Om' complete -T -P "$PREFIX" -D "second in localized alternative numeral" '%OS' complete -T -P "$PREFIX" -D "week of year in localized alternative numeral" '%OU' complete -T -P "$PREFIX" -D "day of week in localized alternative numeral for 1-7" '%Ou' complete -T -P "$PREFIX" -D "week of year in localized alternative numeral" '%OV' complete -T -P "$PREFIX" -D "week of year in localized alternative numeral" '%OW' complete -T -P "$PREFIX" -D "day of week in localized alternative numeral for 0-6" '%Ow' complete -T -P "$PREFIX" -D "year within century in localized alternative numeral" '%Oy' complete -T -P "$PREFIX" -D "%" '%%' #<<# case $type in (GNU|*BSD|Darwin|SunOS|HP-UX) #>># complete -T -P "$PREFIX" -D "like %H:%M (e.g. 13:45)" '%R' esac #<<# case $type in (GNU|*BSD|Darwin|SunOS) #>># complete -T -P "$PREFIX" -D "like %Y-%m-%d (e.g. 1999-12-31)" '%F' complete -T -P "$PREFIX" -D "year within century corresponding to %V [00-99]" '%g' complete -T -P "$PREFIX" -D "year corresponding to %V, full (e.g. 1999)" '%G' complete -T -P "$PREFIX" -D "hour [ 0-23] (space-padded)" '%k' complete -T -P "$PREFIX" -D "hour [ 1-12] (space-padded)" '%l' complete -T -P "$PREFIX" -D "RFC 2822 style numeric timezone (e.g. +0530)" '%z' esac #<<# case $type in (GNU|*BSD|Darwin) #>># complete -T -P "$PREFIX" -D "seconds since epoch in numeral" '%s' esac #<<# case $type in (*BSD|Darwin) #>># complete -T -P "$PREFIX" -D "like %e-%b-%Y" '%v' complete -T -P "$PREFIX" -D "the default localized format" '%+' esac case $type in (GNU|SunOS) #>># complete -T -P "$PREFIX" -D "year within century corresponding to %V in localized alternative numeral" '%Og' esac #<<# case $type in (GNU) #>># complete -T -P "$PREFIX" -D "nanosecond within second [000000000-999999999]" '%N' complete -T -P "$PREFIX" -D "AM or PM, localized, lower case" '%P' complete -T -P "$PREFIX" -D "RFC 2822 style numeric timezone (e.g. +05:30)" '%:z' complete -T -P "$PREFIX" -D "RFC 2822 style numeric timezone (e.g. +05:30:00)" '%::z' complete -T -P "$PREFIX" -D "RFC 2822 style numeric timezone with minimal colons" '%:::z' ;; #<<# (SunOS) #>># complete -T -P "$PREFIX" -D "year corresponding to %V, full, alternative format" '%EG' complete -T -P "$PREFIX" -D "year within era corresponding to %V, in numeral, alternative format" '%Eg' ;; #<<# (HP-UX) #>># complete -T -P "$PREFIX" -D "name of era" '%N' complete -T -P "$PREFIX" -D "year within era" '%o' ;; #<<# esac esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/df000066400000000000000000000102301354143602500162020ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "df" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/df { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "k; print figures in kilobytes (1024-byte units)" "P ${long:+--portability}; print in the portable format defined by POSIX" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "l ${long:+--local}; print info about local file systems only" ) #<# case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "h ${long:+--human-readable}; print size using K, M, etc. for 1024^n" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "a ${long:+--all}; print all file systems" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "m; print figures in megabytes (1048576-byte units)" ) #<# case $type in (GNU|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "H ${long:+--si}; print size using k, M, etc. for 1000^n" ) #<# case $type in (GNU|FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "T ${long:+--print-type}; print file system types" ) #<# esac esac case $type in (FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "g; print figures in gigabytes (1073741824-byte units)" ) #<# case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "b; print figures in 512-byte units" ) #<# esac esac esac esac case $type in (*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "n; allow printing cached data that may be out of date" ) #<# esac esac case $type in (GNU|*BSD|Darwin|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "i ${long:+--inodes}; print inode information" ) #<# esac case $type in (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "b; print the size of free area in kilobytes" "e; print the number of files free" "F:; specify a file system type to print" "g; print all the results of the statvfs function" "n; print file system types" "o:; specify options specific to file systems" "v; print block counts" "V; print full command lines" ) #<# esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "B: --block-size:; specify the block size unit to print figures in" "--total; print the grand total after the normal output" "--no-sync; allow printing cached data that may be out of date" "--sync; don't use cached data that may be out of date" "x: --exclude-type:; specify a file system type not to print" "--help" "--version" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "c; print the grand total" ) #<# ;; (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "G; print all the results of the statvfs function" ) #<# ;; (Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "T:; specify a file system type to print" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "Z; print file systems in all zones" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "f; print the number of blocks in the free list" "s; don't sync the file system data on the disk" ) #<# ;; esac case $type in (GNU|*BSD) ADDOPTIONS=("$ADDOPTIONS" #># "t: ${long:+--type:}; specify a file system type to print" ) #<# ;; (*) POSIXOPTIONS=("$POSIXOPTIONS" #># "t; include total counts in the output" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (B|--block-size) if command -vf completion//completeblocksize >/dev/null 2>&1 || . -AL completion/_blocksize; then command -f completion//completeblocksize fi ;; (?|-?*) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/diff000066400000000000000000000171521354143602500165330ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "diff" command. # Supports POSIX 2008, GNU diffutils 3.0, OpenBSD 4.8, SunOS 5.10, HP-UX 11i v3. function completion/diff { case $("${WORDS[1]}" --version 2>/dev/null) in (*'diffutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "b ${long:+--ignore-space-change}; ignore changes in amount of whitespaces only" "C: ${long:+--context::}; output in copied context format with the specified number of context lines" "c; like -C 3" "e ${long:+--ed}; output in ed script format" "f ${long:+--forward-ed}; output in forward ed format" "r ${long:+--recursive}; recursively compare directories" "U: ${long:+--unified::}; output in unified context format with the specified number of context lines" "u; like -U 3" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "D: ${long:+--ifdef:}; output in merged #ifdef format with the specified conditional macro name" "i ${long:+--ignore-case}; case-insensitive comparison" "l ${long:+--paginate}; output in long format thru the pr command" "n ${long:+--rcs}; output in rcsdiff format" "S: ${long:+--starting-file:}; restart directory comparison from the specified file" "s ${long:+--report-identical-files}; explicitly report identical files" "t ${long:+--expand-tabs}; expand tabs in output" "w ${long:+--ignore-all-space}; ignore whitespaces in comparison" "X: ${long:+--exclude-from:}; skip files whose names match a pattern in the specified file" "x: ${long:+--exclude:}; skip files whose names match the specified pattern" ) #<# esac case $type in (GNU|*BSD) ADDOPTIONS=("$ADDOPTIONS" #># "a ${long:+--text}; assume all files are text" "d ${long:+--minimal}; make the output as small as possible" "I: ${long:+--ignore-matching-lines:}; don't output changes matching the specified regular expression" "L: ${long:+--label:}; specify the label used instead of the filename" "N ${long:+--new-file}; treat absent files as empty" "P ${long:+--unidirectional-new-file}; treat absent from-files as empty" "p ${long:+--show-c-function}; print the name of C function containing changes" "q ${long:+--brief}; just print the names of differing files" "T ${long:+--initial-tab}; prepend a tab to each output line to align text" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "B --ignore-blank-lines; ignore insertion/deletion of empty lines" "--binary; don't convert CR-LF into LF" "--changed-group-format:; specify the format of differing lines" "E --ignore-tab-expansion; ignore changes due to tab expansion" "F: --show-function-line:; specify a regular expression to find the name of the function containing the change" "--from-file:; specify a file to compare with each operand" "--horizon-lines:; don't discard the specified number of lines in the common prefix/suffix" "--ignore-file-name-case; case-insensitive filename comparison" "--left-column; print only the left column of two common lines (with -y)" "--line-format:; specify the --{old,new,unchanged}-line-format options at once" "--new-group-format:; specify the format of lines only in the second file" "--new-line-format:; specify the format of a line only in the second file" "--no-ignore-file-name-case; case-sensitive filename comparison" "--normal; output in normal format" "--old-group-format:; specify the format of lines only in the first file" "--old-line-format:; specify the format of a line only in the first file" "--speed-large-files; fast, half-hearted comparison" "--strip-trailing-cr; ignore carriage returns at the end of lines" "--suppress-blank-empty; don't end a line with a space unless there was one in the text" "--suppress-common-lines; don't print common lines (with -y)" "--tabsize:; specify how many spaces a tab stands for" "--to-file:; specify a file to compare each operand with" "--unchanged-group-format:; specify the format of lines common to the both files" "--unchanged-line-format:; specify the format of a line common to the both files" "v --version; print version info" "W: --width:; specify the max output width (with -y)" "y --side-by-side; output in side-by-side format" "--help" ) #<# esac case $type in (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "h; fast, half-hearted comparison" ) #<# esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([CUW]|--context|--unified|--horizon-lines|--tabsize|--width) ;; (D|--ifdef) if [ -r tags ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 tags 2>/dev/null) fi ;; (--*line-format) command -f completion/diff::line ;; (--*group-format) command -f completion/diff::group ;; (*) complete -P "$PREFIX" -f ;; esac } function completion/diff::line { typeset word="${TARGETWORD#"$PREFIX"}" word=${word//%%} case $word in (*%|*%[doxX]) PREFIX=${TARGETWORD%\%*} #>># complete -T -P "$PREFIX" -D "line contents, without newline" '%l' complete -T -P "$PREFIX" -D "line contents, with newline" '%L' complete -T -P "$PREFIX" -D "escape a following character enclosed in single-quotes" '%c' complete -T -P "$PREFIX" -D "line number in decimal" '%dn' complete -T -P "$PREFIX" -D "line number in octal" '%on' complete -T -P "$PREFIX" -D "line number in hexadecimal, lowercase" '%xn' complete -T -P "$PREFIX" -D "line number in hexadecimal, uppercase" '%Xn' complete -T -P "$PREFIX" -D "%" '%%' return 0 #<<# ;; esac } function completion/diff::group { typeset word="${TARGETWORD#"$PREFIX"}" word=${word//%%} case $word in (*%[doxX]) PREFIX=${TARGETWORD%\%?} word=${TARGETWORD#"$PREFIX"} #>># complete -T -P "$PREFIX" -D "line number just before the group in the first file" "${word}e" complete -T -P "$PREFIX" -D "line number just before the group in the second file" "${word}E" complete -T -P "$PREFIX" -D "line number of the first line in the group in the first file" "${word}f" complete -T -P "$PREFIX" -D "line number of the first line in the group in the second file" "${word}F" complete -T -P "$PREFIX" -D "line number of the last line in the group in the first file" "${word}l" complete -T -P "$PREFIX" -D "line number of the last line in the group in the second file" "${word}L" complete -T -P "$PREFIX" -D "line number just after the group in the first file" "${word}m" complete -T -P "$PREFIX" -D "line number just after the group in the second file" "${word}M" complete -T -P "$PREFIX" -D "number of lines in the group in the first file" "${word}n" complete -T -P "$PREFIX" -D "number of lines in the group in the second file" "${word}N" return 0 #<<# ;; (*%) PREFIX=${TARGETWORD%\%} #>># complete -T -P "$PREFIX" -D "lines from the first file" '%<' complete -T -P "$PREFIX" -D "lines from the second file" '%>' complete -T -P "$PREFIX" -D "lines common to the both files" '%=' complete -T -P "$PREFIX" -D "escape a following character enclosed in single-quotes" '%c' complete -T -P "$PREFIX" -D "format a decimal number" '%d' complete -T -P "$PREFIX" -D "format an octal number" '%o' complete -T -P "$PREFIX" -D "format a hexadecimal number in lowercase" '%x' complete -T -P "$PREFIX" -D "format a hexadecimal number in uppercase" '%X' complete -T -P "$PREFIX" -D "conditional expression" '%(' complete -T -P "$PREFIX" -D "%" '%%' return 0 #<<# ;; esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/dirs000066400000000000000000000002661354143602500165620ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "dirs" built-in command. function completion/dirs { command -f completion//reexecute cd } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/disown000066400000000000000000000002741354143602500171230ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "disown" built-in command. function completion/disown { command -f completion//reexecute jobs } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/dnf000066400000000000000000000140331354143602500163650ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "dnf" command. # Supports dnf 1.1.9. function completion/dnf { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "4; use IPv4 addresses only" "6; use IPv6 addresses only" "--allowerasing; allow erasing conflicting packages" "--assumeno; reject all questions" "b --best; consider latest packages only" "C --cacheonly; use local system cache only" "c: --config:; specify a config file" "d: --debuglevel:" # deprecated "--debugsolver" "--disableexcludes:; disable excludes for the specified repo" "--disablerepo:" "--downloadonly" "e: --errorlevel:" # deprecated "--enablerepo:" "x: --exclude:; specify packages to exclude" "h --help; print help" "--installroot:" "--nogpgcheck; skip GPG check" "--noplugins; disable all plugins" "q --quiet; don't print progress" "R: --randomwait:; specify max minutes to wait" "--refresh; refresh metadata" "--releasever:; assume the specified system release version" "--repofrompath:; temporarily add the specified repository" "--rpmverbosity:; debug output level of the rpm command" "--setopt:; override a config option" "--showduplicates" "v --verbose; print debug messages" "--version; print version info" "--assumeyes; accept all questions" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; ('') # find first non-option argument and # parse some global options typeset OPTIND=2 subcmd= while [ $OPTIND -le ${WORDS[#]} ]; do case ${WORDS[OPTIND]} in (-?*) ;; (*) subcmd=${WORDS[OPTIND]} break ;; esac OPTIND=$((OPTIND+1)) done if [ $OPTIND -le ${WORDS[#]} ]; then # complete command argument typeset OLDWORDS OLDWORDS=("$WORDS") WORDS=("${WORDS[1]}" "${WORDS[OPTIND+1,-1]}") if { command -vf "completion/dnf::$subcmd:arg" || . -AL "completion/dnf_$subcmd"; } >/dev/null 2>&1; then command -f "completion/dnf::$subcmd:arg" else # Unknown subcommand... This fallback should work for most subcommands. command -f completion/dnf::completepackage available complete -P "$PREFIX" -f fi else command -f completion/dnf::completecmd fi ;; esac } function completion/dnf::completion_helper { ${$(head -n 1 -- "$(command -v dnf)")#\#!} -m dnf.cli.completion_helper "$@" } 2>/dev/null function completion/dnf::complete_by_helper { typeset IFS=' ' complete -P "$PREFIX" $(command -f completion/dnf::completion_helper "$@") } function completion/dnf::completecmd { command -f completion/dnf::complete_by_helper _cmds "${TARGETWORD#"$PREFIX"}" } # $1 = installed, available, updates function completion/dnf::completepackage { command -f completion/dnf::complete_by_helper list -Cq "$1" "${TARGETWORD#"$PREFIX"}" } function completion/dnf::check-update:arg { command -f completion/dnf::completepackage installed } function completion/dnf::clean:arg { command -f completion/dnf::complete_by_helper clean -Cq DUMMY "${TARGETWORD#"$PREFIX"}" } function completion/dnf::distro-sync:arg { command -f completion/dnf::completepackage installed } function completion/dnf::distribution-synchronization:arg { command -f completion/dnf::completepackage installed } function completion/dnf::downgrade:arg { command -f completion/dnf::complete_by_helper downgrade -Cq "${TARGETWORD#"$PREFIX"}" } function completion/dnf::erase:arg { command -f completion/dnf::complete_by_helper remove -Cq "${TARGETWORD#"$PREFIX"}" } # TODO #function completion/dnf::group:arg { #} function completion/dnf::help:arg { command -f completion/dnf::completecmd } function completion/dnf::history:arg { command -f completion/dnf::complete_by_helper history -Cq DUMMY "${TARGETWORD#"$PREFIX"}" # TODO complete second operand } function completion/dnf::info:arg { command -f completion/dnf::completepackage available } function completion/dnf::install:arg { command -f completion/dnf::complete_by_helper install -Cq "${TARGETWORD#"$PREFIX"}" } function completion/dnf::list:arg case ${WORDS[#]} in (1) command -f completion/dnf::complete_by_helper list -Cq DUMMY "${TARGETWORD#"$PREFIX"}" ;; (*) command -f completion/dnf::completepackage available ;; esac function completion/dnf::makecache:arg { #>># complete -P "$PREFIX" timer } #<<# function completion/dnf::mark:arg case ${WORDS[#]} in (1) #>># complete -P "$PREFIX" install remove ;; #<<# (*) command -f completion/dnf::completepackage installed ;; esac function completion/dnf::provides:arg { complete -P "$PREFIX" -f } function completion/dnf::reinstall:arg { command -f completion/dnf::complete_by_helper reinstall -Cq "${TARGETWORD#"$PREFIX"}" } function completion/dnf::remove:arg { command -f completion/dnf::complete_by_helper remove -Cq "${TARGETWORD#"$PREFIX"}" } function completion/dnf::repolist:arg { #>># complete -P "$PREFIX" all enabled disabled } #<<# # TODO #function completion/dnf::repository-packages:arg { #} # TODO #function completion/dnf::search:arg { #} function completion/dnf::update:arg { command -f completion/dnf::upgrade:arg "$@" } function completion/dnf::updateinfo:arg { #>># complete -P "$PREFIX" -D 'show advisories on all package versions' all complete -P "$PREFIX" -D 'show advisories on newer packages' available complete -P "$PREFIX" -D 'print detail' info complete -P "$PREFIX" -D 'show advisories on installed or older packages' installed complete -P "$PREFIX" -D 'list update advisories' list complete -P "$PREFIX" -D 'just print number of updates' summary complete -P "$PREFIX" -D 'show advisories on available newer packages' updates #<<# command -f completion/dnf::completepackage updates } function completion/dnf::upgrade:arg { command -f completion/dnf::completepackage updates } function completion/dnf::update-to:arg { command -f completion/dnf::upgrade-to:arg "$@" } function completion/dnf::upgrade-to:arg { command -f completion/dnf::completepackage updates } function completion/dnf::whatprovides:arg { command -f completion/dnf::provides:arg "$@" } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/du000066400000000000000000000125101354143602500162240ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "du" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/du { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "a ${long:+--all}; print size for all files including non-directories" "H ${long:+--dereference-args}; follow symbolic links in operands" "k; print figures in kilobytes (1024-byte units)" "L ${long:+--dereference}; follow all symbolic links" "s ${long:+--summarize}; don't print subdirectory sizes" "x ${long:+--one-file-system}; count disk usage only in the same file system" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "h ${long:+--human-readable}; print size using K, M, etc. for 1024^n" ) #<# case $type in (GNU|*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "c ${long:+--total}; print the grand total after the normal output" "P ${long:+--no-dereference}; don't follow symbolic links" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "m; print figures in megabytes (1048576-byte units)" ) #<# case $type in (GNU|FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "l ${long:+--count-links}; count size as many times as the file's hard links" ) #<# esac case $type in (FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "d:; specify directory depth to limit printing" ) #<# case $type in (FreeBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "n; ignore files with the nodump flag" ) #<# esac case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "I:; specify a glob pattern for files to ignore" ) #<# esac case $type in (NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "g; print figures in gigabytes (1073741824-byte units)" ) #<# esac esac esac esac esac case $type in (*BSD|Darwin|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "r; warn about unreadable directories and unaccessible files" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "0 --null; separate output lines with null byte rather than newline" "B: --block-size:; specify the block size unit to print figures in" "b --bytes; like -B 1 --apparent-size" "S --separate-dirs; don't include subdirectories' size in the parent's size" "X: --exclude-from:; skip files whose names match a pattern in the specified file" "--apparent-size; print apparent file sizes rather than actual disk usage" "--exclude:; skip files whose names match the specified pattern" "--files0-from:; specify a file containing null-separated file names to count size" "--max-depth:; specify directory depth to limit printing" "--si:; print size using k, M, etc. for 1000^n" "--time:; specify which time to print" "--time-style:; specify format to print time in" "--help" "--version" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "A; print apparent file sizes rather than actual disk usage" "B:; specify block size assumed in disk usage estimation" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "d; count disk usage only in the same file system" "o; don't include subdirectories' size in the parent's size" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "b; print the number of blocks used by swap" "t:; specify a file system type to print" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (B|--block-size) case $type in (GNU) if command -vf completion//completeblocksize >/dev/null 2>&1 || . -AL completion/_blocksize; then command -f completion//completeblocksize fi ;; esac ;; (d) ;; #(I) -- complete as filename as usual # ;; (t) ;; #(X|--exclude-from|--exclude|--files0-from) -- complete as filename as usual # ;; (--max-depth) ;; (--time) #>># complete -P "$PREFIX" -D "print or sort by last access time" atime access use complete -P "$PREFIX" -D "print or sort by last status change time" ctime status ;; #<<# (--time-style) case $TARGETWORD in ("$PREFIX+"*) if command -vf completion//completestrftime >/dev/null 2>&1 || . -AL completion/date; then command -f completion//completestrftime fi ;; (*) #>># complete -P "$PREFIX" -D "e.g. 2000-01-01 01:23:45.678901234 +0900" full-iso complete -P "$PREFIX" -D "e.g. 2010-06-29 00:37" long-iso complete -P "$PREFIX" -D "e.g. 01-23 01:23 or 2000-01-23" iso complete -P "$PREFIX" -D "locale-dependent time style" locale posix-locale complete -P "$PREFIX" -D "like full-iso, but only when not in POSIX locale" posix-full-iso complete -P "$PREFIX" -D "like long-iso, but only when not in POSIX locale" posix-long-iso complete -P "$PREFIX" -D "like iso, but only when not in POSIX locale" posix-iso complete -T -P "$PREFIX" -D "specify time format after +" + ;; #<<# esac ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/echo000066400000000000000000000033631354143602500165400ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "echo" built-in command. function completion/echo { command -f completion/echo::option || command -f completion/echo::operand } function completion/echo::option { case ${ECHO_STYLE-} in ([BbDd]*) typeset options=n ;; ([GgZz]*) typeset options=neE ;; (*) return 1 ;; esac typeset word for word in "${WORDS[2,-1]}"; do case $word in (-?*) case ${word#-} in (*[!$options]*) return 1 esac ;; (*) return 1 ;; esac done case $TARGETWORD in (-*) case ${TARGETWORD#-} in (*[!$options]*) return 1 esac # option completion case $options in (*n*) #>># complete -P "$TARGETWORD" -D "don't print the last newline" -O n esac #<<# case $options in (*e*) #>># complete -P "$TARGETWORD" -D "enable escape sequences" -O e esac #<<# case $options in (*E*) #>># complete -P "$TARGETWORD" -D "disable escape sequences" -O E esac #<<# return 0 esac return 1 } function completion/echo::operand { typeset options escape case ${ECHO_STYLE-} in ([Bb]*) options=n escape=false ;; ([Dd]*) options=n escape=true ;; ([Gg]*) options=neE escape=false ;; ([Zz]*) options=neE escape=true ;; ([Rr]*) options= escape=false ;; (*) options= escape=true ;; esac typeset word for word in "${WORDS[2,-1]}"; do case $word in (-?*) case ${word#-} in (*[!$options]*) break esac case ${word//n} in (*E) escape=false ;; (*e) escape=true ;; esac ;; (*) break ;; esac done if $escape; then if command -vf completion/printf::backslash >/dev/null 2>&1 || . -AL completion/printf; then command -f completion/printf::backslash echo fi fi complete -f } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ed000066400000000000000000000027461354143602500162160ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ed" command. # Supports POSIX 2008, GNU 1.1, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/ed { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "p: ${long:+--prompt:}; specify a prompt string" "s ${long:+--silent --quiet}; suppress diagnostic messages" ) #<# ADDOPTIONS=() case $type in (*BSD|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "x; enable encryption" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "G --traditional; enable compatibility mode" "l --loose-exit-status; always exit with 0 status" "v --verbose; print detailed error messages" "h --help; print help" "V --version; print version info" ) #<# ;; (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "E; use extended regular expression" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "C; enable encryption and assume all text has been encrypted" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (p|--prompt) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/egrep000066400000000000000000000003161354143602500167170ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "egrep" command. function completion/egrep { WORDS=(grep -E "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/env000066400000000000000000000037511354143602500164130ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "env" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/env { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "i ${long:+--ignore-environment}; ignore variables exported from the shell" ) #<# case $type in (GNU|FreeBSD) OPTIONS=("$OPTIONS" #># "u: ${long:+--unset:}; specify a variable to remove from the environment" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "0 --null; separate variables by null bytes in printing" "--help" "--version" ) #<# ;; (FreeBSD) OPTIONS=("$OPTIONS" #># "P:; specify directory paths to search for the invoked command" "S:; specify a string to be parsed before processing" "v; describe what is going on during the process" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (P) typeset targetword="${TARGETWORD#"$PREFIX"}" targetword=${targetword##*:} PREFIX=${TARGETWORD%"$targetword"} complete -T -P "$PREFIX" -S / -d ;; (S) ;; (u|--unset) complete -P "$PREFIX" -v ;; (*) command -f completion//getoperands typeset i=1 while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (*=*) ;; (*) WORDS=("${WORDS[i,-1]}") command -f completion//reexecute return ;; esac i=$((i+1)) done case $TARGETWORD in (*=*) typeset targetword="${TARGETWORD#"$PREFIX"}" targetword=${targetword#*=} targetword=${targetword##*:} PREFIX=${TARGETWORD%"$targetword"} complete -P "$PREFIX" -f ;; (*) complete -T -S = -v WORDS=() command -f completion//reexecute -e ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/eval000066400000000000000000000012561354143602500165500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "eval" built-in command. function completion/eval { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "i --iteration; perform iterative execution on each operand" "--help" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset iteration=false word for word in "${WORDS[2,-1]}"; do case $word in (-i|--iteration) iteration=true ;; (--) break ;; esac done if $iteration; then complete -c else command -f completion//getoperands command -f completion//reexecute fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/eview000066400000000000000000000003041354143602500167310ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "eview" command. # Supports Vim 7.3. function completion/eview { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/evim000066400000000000000000000003021354143602500165500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "evim" command. # Supports Vim 7.3. function completion/evim { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ex000066400000000000000000000041641354143602500162360ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ex" command. # Supports POSIX 2008, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, SunOS 5.10, # HP-UX 11i v3. function completion/ex { case $("${WORDS[1]}" --version 2>/dev/null) in (VIM*) command -f completion//reexecute vim return esac typeset type="$(uname 2>/dev/null)" typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "c:; specify a command to be executed after starting up" "R; read-only mode" "r; recover unsaved files" "t:; specify an identifier to jump" "w:; specify the value of the window option" ) #<# case ${WORDS[1]##*/} in (e*) POSIXOPTIONS=("$POSIXOPTIONS" #># "s; non-interactive batch processing mode" "v; operate as the vi editor" ) #<# esac ADDOPTIONS=() case $type in (*BSD) ADDOPTIONS=("$ADDOPTIONS" #># "F; don't copy the entire file when opening it" "S; enable the secure option" ) #<# case ${WORDS[1]##*/} in (v*) ADDOPTIONS=("$ADDOPTIONS" #># "e; operate as the ex editor" ) #<# esac case $type in (FreeBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "G; enable the gtagsmode option" ) #<# esac esac case $type in (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "C; enable encryption and assume all text has been encrypted" "l; enable the lisp option" "x; enable encryption" ) #<# esac case $type in (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "L; print list of recoverable files" "V; echo input commands to the standard error" ) #<# case ${WORDS[1]##*/} in (v*) ADDOPTIONS=("$ADDOPTIONS" #># "S; don't assume the tags file is sorted" ) #<# esac ;; (HP-UX) case ${WORDS[1]##*/} in (v*) ADDOPTIONS=("$ADDOPTIONS" #># "V; echo input commands when sourcing a file" ) #<# esac ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (c) ;; (t) if [ -r tags ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 tags 2>/dev/null) fi ;; (w) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/exec000066400000000000000000000011351354143602500165410ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "exec" built-in command. function completion/exec { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a: --as:; specify a name to execute the command as" "c --clear; don't export variables from the shell" "f --force; suppress warning about stopped jobs" "--help" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (a|--as) complete -P "$PREFIX" -c ;; (*) command -f completion//getoperands command -f completion//reexecute -e ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/exit000066400000000000000000000006021354143602500165640ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "exit" built-in command. function completion/exit { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f --force; suppress warning about stopped jobs" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/expand000066400000000000000000000016401354143602500170750ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "expand" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/expand { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "t: ${long:+--tabs:}; specify a tab width or a list of tab positions" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "i --initial; only expand first tabs on each line" "--help" "--version" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (t|--tabs) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/export000066400000000000000000000002771354143602500171440ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "export" built-in command. function completion/export { command -f completion//reexecute typeset } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/fc000066400000000000000000000015221354143602500162050ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "fc" built-in command. function completion/fc { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "e: --editor:; specify an editor to edit the command" "l --list; just print command history" "n --no-numbers; don't print history numbers in listing" "q --quiet; don't print the command before re-executing" "r --reverse; reverse the order of commands" "s --silent; re-execute the command without editing" "v --verbose; print history in detail (with -l)" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (e|--editor) complete -P "$PREFIX" -c --normal-alias ;; (*) typeset num cmd while read -r num cmd; do complete -D "$num" -- "$cmd" done <(fc -l 1) ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/fg000066400000000000000000000002641354143602500162130ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "fg" built-in command. function completion/fg { command -f completion//reexecute jobs } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/fgrep000066400000000000000000000003161354143602500167200ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "fgrep" command. function completion/fgrep { WORDS=(grep -F "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/file000066400000000000000000000052651354143602500165440ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "file" command. # Supports POSIX 2008, Fine Free File 5.04, SunOS 5.10, HP-UX 11i v3. function completion/file { if "${WORDS[1]}" --help >/dev/null 2>&1; then typeset type=FFF else typeset type="$(uname 2>/dev/null)" fi case $type in (FFF) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "h ${long:+--no-dereference}; don't follow symbolic links" "m: ${long:+--magic-file:}; specify magic files" ) #<# case $type in (FFF|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "c ${long:+--checking-printout}; check the format of the magic file" "f: ${long:+--files-from:}; specify a file containing filenames to examine" ) #<# esac case $type in (FFF) OPTIONS=("$OPTIONS" #># "0 --print0; separate output lines with null byte rather than newline" "b --brief; don't print the filename before each file type description" "C --compile; compile a magic file" "d --debug; print debugging messages" "e: --exclude:; specify a type of test to skip" "F: --separator:; specify a separator shown between filenames and file types" "i --mime; print mime types/encodings rather than human-readable results" "k --keep-going; print all matching file types" "L --dereference; follow symbolic links" "N --no-pad; don't align output" "n --no-buffer; don't buffer output" "p --preserve-date; restore the access time of examined files" "r --raw; don't escape unprintable characters" "s --special-files; read and examine non-regular files" "v --version; print version info" "z --uncompress; examine contents of compressed files" "--apple; print Apple creator/type" "--help" "--mime-type; print mime types only" "--mime-encoding; print mime encoding names only" ) #<# ;; (*) # POSIX options OPTIONS=("$OPTIONS" #># "d; do position/context-sensitive default system tests" "i; don't examine the contents of regular files" "M:; specify a file containing position-sensitive tests" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (e|--exclude) #>># complete -P "$PREFIX" -D "EMX application type" apptype complete -P "$PREFIX" -D "text encoding" encoding complete -P "$PREFIX" -D "text tokens" tokens complete -P "$PREFIX" -D "Compound Document Files details" cdf complete -P "$PREFIX" -D "contents of compressed files" compress complete -P "$PREFIX" -D "ELF file details" elf complete -P "$PREFIX" -D "magic file test" soft complete -P "$PREFIX" -D "contents of tar files" tar ;; #<<# (F|--separator) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/find000066400000000000000000000447421354143602500165500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "find" command. # Supports POSIX 2008, GNU findutils 4.5.9, FreeBSD 8.1, OpenBSD 4.8, # NetBSD 5.0, Darwin 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/find { typeset SAVEWORDS SAVEWORDS=("$WORDS") case $("${WORDS[1]}" --version 2>/dev/null) in (*'findutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "H; follow symbolic links in operands" "L; follow all symbolic links" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "D:; specify debug options" "O::; specify optimization level" "P; don't follow symbolic links" "--help" "--version" ) #<# ;; (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "d; do post-order traversal rather than pre-order" "f:; specify a directory to search" "X; warn about filenames incompatible with xargs" "x; don't search in different file systems" ) #<# case $type in (FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "E; use extended regular expression" "P; don't follow symbolic links" "s; sort filenames within each directory" ) #<# esac ;; esac command -f completion//parseoptions case $ARGOPT in (-) case $TARGETWORD in (-|--*) command -f completion//completeoptions esac if [ "$type" = GNU ]; then typeset i=2 command -f completion/find::expr fi ;; (D) if [ "$PREFIX" ]; then complete -P "$PREFIX" "" else typeset targetword="${TARGETWORD##*,}" PREFIX=${TARGETWORD%"$targetword"} #>># complete -P "$PREFIX" -D "print help about the -D option" help complete -P "$PREFIX" -D "print the syntax tree of the expression" tree complete -P "$PREFIX" -D "print info during directory tree search" search complete -P "$PREFIX" -D "trace calls to the stat function" stat complete -P "$PREFIX" -D "print success rate for each predicate" rates complete -P "$PREFIX" -D "print debug info about optimization" opt complete -P "$PREFIX" -D "print debug info about execution of external commands" exec #<<# fi ;; (f) complete -P "$PREFIX" -S / -T -d ;; (O) ;; (*) typeset path=false expr=false i=2 WORDS=("$SAVEWORDS") if [ "$type" = GNU ]; then path=true fi while [ $i -le ${WORDS[#]} ]; do if [ "${WORDS[i]}" = -- ]; then i=$((i+1)) break fi case $type in (GNU) case ${WORDS[i]} in (-[!DHLOP-]*) break esac ;; (*BSD|Darwin) case ${WORDS[i]} in (-f*) path=true esac ;; esac case ${WORDS[i]} in (-?*) ;; (*) break ;; esac i=$((i+1)) done while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (\(|!|-*) expr=true break ;; (*) path=true ;; esac i=$((i+1)) done if ! $expr; then complete -S / -T -d fi if $path; then command -f completion/find::expr fi ;; esac } # This function depends on variables $i, $type. function completion/find::expr { while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (-acl|-aclv) case $type in (HP-UX) if [ $i -eq ${WORDS[#]} ]; then #>># complete -D "optional access control list entries" opt fi #<<# i=$((i+2)) ;; (*) i=$((i+1)) ;; esac ;; (-[acmB]newer|-fls|-fprint|-fprint0|-ilname|-iname|-ipath|-iwholename|-linkedto|-lname|-name|-newer*|-path|-samefile|-wholename) if [ $i -eq ${WORDS[#]} ]; then complete -f fi i=$((i+2)) ;; (-[acmB]time) if [ $i -eq ${WORDS[#]} ]; then case $type in (FreeBSD|Darwin) case $TARGETWORD in (*[[:digit:]]*) PREFIX=${TARGETWORD%"${TARGETWORD##*[[:digit:]]}"} #>># complete -P "$PREFIX" -D "second" s complete -P "$PREFIX" -D "minute" m complete -P "$PREFIX" -D "hour" h complete -P "$PREFIX" -D "day" d complete -P "$PREFIX" -D "week" w #<<# esac esac fi i=$((i+2)) ;; (-exec|-execdir|-ok|-okdir) i=$((i+1)) typeset j=$i while [ $j -le ${WORDS[#]} ]; do if [ "${WORDS[j]}" = ";" ]; then i=$((j+1)); break elif [ "${WORDS[j]} ${WORDS[j+1]}" = "{} +" ]; then i=$((j+2)); break fi j=$((j+1)) done if [ $i -le $j ]; then WORDS=("${WORDS[i,-1]}") command -f completion//reexecute -e return fi ;; (-flags) if [ $i -eq ${WORDS[#]} ]; then # TODO fi i=$((i+2)) ;; (-fprintf) if [ $i -eq ${WORDS[#]} ]; then complete -f elif [ $((i+1)) -eq ${WORDS[#]} ]; then command -f completion/find::printf fi i=$((i+3)) ;; (-group) if [ $i -eq ${WORDS[#]} ]; then complete -g fi i=$((i+2)) ;; (-perm) if [ $i -eq ${WORDS[#]} ]; then if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode find fi fi i=$((i+2)) ;; (-printf) if [ $i -eq ${WORDS[#]} ]; then command -f completion/find::printf fi i=$((i+2)) ;; (-regextype) if [ $i -eq ${WORDS[#]} ]; then complete emacs posix-awk posix-basic posix-egrep posix-extended fi i=$((i+2)) ;; (-size) if [ $i -eq ${WORDS[#]} ]; then case $TARGETWORD in (*[[:digit:]]*) PREFIX=${TARGETWORD%"${TARGETWORD##*[[:digit:]]}"} #>># complete -P "$PREFIX" -D "byte" c #<<# case $type in (GNU|FreeBSD|Darwin) #>># complete -P "$PREFIX" -D "kilobyte (2^10 bytes)" k complete -P "$PREFIX" -D "megabyte (2^20 bytes)" M complete -P "$PREFIX" -D "gigabyte (2^30 bytes)" G #<<# case $type in (GNU) #>># complete -P "$PREFIX" -D "block (2^9 bytes)" b complete -P "$PREFIX" -D "word (2 bytes)" w ;; #<<# (FreeBSD|Darwin) #>># complete -P "$PREFIX" -D "terabyte (2^40 bytes)" T complete -P "$PREFIX" -D "petabyte (2^50 bytes)" P ;; #<<# esac esac esac fi i=$((i+2)) ;; (-type|-xtype) if [ $i -eq ${WORDS[#]} ]; then #>># complete -D "block special file" b complete -D "character special file" c complete -D "directory" d complete -D "regular file" f complete -D "symbolic link" l complete -D "FIFO" p complete -D "socket" s fi #<<# case $(uname) in (SunOS) #>># complete -D "door" D esac #<<# case $type in (NetBSD) #>># complete -D "whiteout" w ;; #<<# (HP-UX) #>># complete -D "mount point" M complete -D "network special file" n ;; #<<# esac i=$((i+2)) ;; (-user) if [ $i -eq ${WORDS[#]} ]; then complete -u fi i=$((i+2)) ;; (-[acmB]min|-context|-cpio|-fsonly|-fstype|-[gu]id|-inum|-iregex|-links|-maxdepth|-mindepth|-ncpio|-regex|-used) i=$((i+2)) ;; (*) i=$((i+1)) ;; esac done if [ $i -eq $((${WORDS[#]}+1)) ]; then case $TARGETWORD in (-newer*) case $type in (GNU|FreeBSD|Darwin|HP-UX) typeset word="${TARGETWORD#-newer}" word=${word#?} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" -D "last access time" a complete -P "$PREFIX" -D "last status change time" c complete -P "$PREFIX" -D "last modified time" m #<<# case $type in (GNU|FreeBSD|Darwin) #>># complete -P "$PREFIX" -D "creation time" B #<<# case $TARGETWORD in (-newer?*) #>># complete -P "$PREFIX" -D "specify time" t esac #<<# esac ;; (*) complete -- -newer ;; esac ;; (-*) #>># complete -D "logical conjunction (and)" -- -a complete -D "true if the last access time is n days ago" -- -atime complete -D "true if the last status change time is n days ago" -- -ctime complete -D "search directories in post-order rather than pre-order" -- -depth complete -D "execute the specified command line" -- -exec complete -D "true if the group is the specified one" -- -group complete -D "true if the file has the specified hard link count" -- -links complete -D "true if the last modified time is n days ago" -- -mtime complete -D "true if the filename matches the specified pattern" -- -name complete -D "logical disjunction (or)" -- -o complete -D "prompt for execution of the specified command line" -- -ok complete -D "true if the pathname matches the specified pattern" -- -path complete -D "true if the file has the specified permission" -- -perm complete -D "print the pathname" -- -print complete -D "don't search the directory if evaluated" -- -prune complete -D "true if the file has the specified size" -- -size complete -D "true if the file has the specified type" -- -type complete -D "true if the owner is the specified one" -- -user complete -D "don't search different file systems" -- -xdev #<<# case $type in (GNU|*BSD|Darwin|SunOS|HP-UX) #>># complete -D "true if the file is on the specified file system" -- -fstype complete -D "true if the file has the specified inode number" -- -inum #<<# case $type in (GNU|*BSD|Darwin|SunOS) #>># complete -D "print the file in ls -dils format" -- -ls #<<# case $type in (GNU|FreeBSD|Darwin|SunOS) #>># complete -D "don't search different file systems" -- -mount esac #<<# esac case $type in (GNU|*BSD|Darwin|SunOS) #>># complete -D "print the pathname with a trailing null byte" -- -print0 esac #<<# esac case $type in (GNU|*BSD|Darwin) #>># complete -D "logical conjunction (and)" -- -and complete -D "true if the last access time is n minutes ago" -- -amin complete -D "true if the last access time is later than last modified time of the specified file" -- -anewer complete -D "true if the last status change time is n minutes ago" -- -cmin complete -D "true if the last status change time is later than last modified time of the specified file" -- -cnewer complete -D "true if the file is empty" -- -empty complete -D "execute the specified command line in the directory containing the file" -- -execdir complete -D "true if the filename matches the specified pattern (case-insensitive)" -- -iname complete -D "search directories of at most the specified depth only" -- -maxdepth complete -D "search directories of at least the specified depth only" -- -mindepth complete -D "true if the last modified time is n minutes ago" -- -mmin complete -D "true if the last modified time is later than that of the specified file" -T -- -newer complete -D "true if the file's group ID has no name" -T -- -nogroup complete -D "true if the file owner's user ID has no name" -T -- -nouser complete -D "prompt for execution of the specified command line in the directory containing the file" -- -okdir complete -D "logical disjunction (or)" -- -or #<<# case $type in (GNU|FreeBSD|NetBSD|Darwin) #>># complete -D "remove the file" -- -delete complete -D "false" -- -false complete -D "true if the pathname matches the specified regular expression (case-insensitive)" -- -iregex complete -D "true if the pathname matches the specified regular expression" -- -regex complete -D "true if the file is a hard link to the specified" -- -samefile #<<# case $type in (GNU|FreeBSD|Darwin) #>># complete -D "true if the file's group ID is the specified" -- -gid complete -D "true if the symbolic link's target matches the specified pattern (case-insensitive)" -- -ilname complete -D "true if the pathname matches the specified pattern (case-insensitive)" -- -ipath -iwholename complete -D "true if the symbolic link's target matches the specified pattern" -- -lname complete -D "true" -- -true complete -D "true if the file owner's user ID is the specified" -- -uid complete -D "true if the pathname matches the specified pattern" -- -wholename esac #<<# esac case $type in (GNU|NetBSD) #>># complete -D "print the pathname to the specified file" -- -fprint esac #<<# case $type in (*BSD|Darwin) #>># complete -D "true if the file has the specified flags" -- -flags #<<# case $type in (FreeBSD|Darwin) #>># complete -D "true if the creation time is n minutes ago" -- -Bmin complete -D "true if the creation time is later than last modified time of the specified file" -- -Bnewer complete -D "true if the creation time is n days ago" -- -Btime complete -D "true if the last modified time is later than that of the specified file" -- -mnewer esac #<<# esac esac case $type in (FreeBSD|SunOS|HP-UX) #>># complete -D "true if the file have additional ACLs defined" -- -acl #<<# case $type in (SunOS|HP-UX) #>># complete -D "write the file in cpio format to the specified device" -- -cpio complete -D "write the file in cpio -c format to the specified device" -- -ncpio complete -D "true if the file is on the same file system" -- -local esac #<<# esac case $type in (GNU) #>># complete -D "true if the SELinux context matches the specified pattern" -- -context complete -D "measure time from the beginning of today" -- -daystart complete -D "true if the file is executable" -- -executable complete -D "write file info to the specified file" -- -fls complete -D "print the null-terminated pathname to the specified file" -- -fprint0 complete -D "print the specified formatted string to the specified file" -- -fprintf complete -D "ignore files removed during traversal" -- -ignore_readdir_race complete -D "disable warning messages" -- -nowarn complete -D "print the specified formatted string" -- -printf complete -D "stop directory traversal" -- -quit complete -D "true if the file is readable" -- -readable complete -D "specify the type of regular expression" -- -regextype complete -D "true if the last access time is n days after the last status change" -- -used complete -D "enable warning messages" -- -warn complete -D "true if the file is writable" -- -writable complete -D "don't search autofs file systems" -- -xautofs complete -D "true if the file has the specified type (do/don't follow symbolic links)" -- -xtype esac #<<# case $type in (NetBSD) #>># complete -D "stop directory traversal" -- -exit complete -D "print the pathname with escapes for xargs" -- -printx complete -D "remove the file" -- -rm esac #<<# case $type in (SunOS) #>># complete -D "true if the file has extended attributes" -- -xattr esac #<<# case $type in (HP-UX) #>># complete -D "true if the file have additional JFS ACLs defined" -- -aclv complete -D "traverse the specified file system only" -- -fsonly complete -D "true if the file is a hard link to the specified" -- -linkedto complete -D "don't search different file systems" -- -mountstop complete -D "don't search the directory unless evaluated" -- -only esac #<<# ;; esac fi return 0 } function completion/find::printf { if command -vf completion/printf::backslash >/dev/null 2>&1 || . -AL completion/printf; then command -f completion/printf::backslash echo fi typeset word="$TARGETWORD" word=${word//%%} case $word in (*%) PREFIX=${TARGETWORD%\%} #>># complete -T -P "$PREFIX" -D "last access time, formatted" '%A' complete -T -P "$PREFIX" -D "last access time" '%a' complete -T -P "$PREFIX" -D "disk usage in 512-byte blocks" '%b' complete -T -P "$PREFIX" -D "last status change time, formatted" '%C' complete -T -P "$PREFIX" -D "last status change time" '%c' complete -T -P "$PREFIX" -D "device number" '%D' complete -T -P "$PREFIX" -D "depth in directory traversal" '%d' complete -T -P "$PREFIX" -D "file system type" '%F' complete -T -P "$PREFIX" -D "basename" '%f' complete -T -P "$PREFIX" -D "group ID (numeric)" '%G' complete -T -P "$PREFIX" -D "group" '%g' complete -T -P "$PREFIX" -D "the searched directory under which the file was found" '%H' complete -T -P "$PREFIX" -D "dirname" '%h' complete -T -P "$PREFIX" -D "inode number" '%i' complete -T -P "$PREFIX" -D "disk usage in kilobyte blocks" '%k' complete -T -P "$PREFIX" -D "target of symbolic link" '%l' complete -T -P "$PREFIX" -D "permission mode bits (symbolic)" '%M' complete -T -P "$PREFIX" -D "permission mode bits (octal)" '%m' complete -T -P "$PREFIX" -D "number of hard links" '%n' complete -T -P "$PREFIX" -D "pathname relative to the searched directory" '%P' complete -T -P "$PREFIX" -D "pathname" '%p' complete -T -P "$PREFIX" -D "sparseness" '%S' complete -T -P "$PREFIX" -D "file size in bytes" '%s' complete -T -P "$PREFIX" -D "last modified time, formatted" '%T' complete -T -P "$PREFIX" -D "last modified time" '%t' complete -T -P "$PREFIX" -D "owner's user ID (numeric)" '%U' complete -T -P "$PREFIX" -D "owner's user name" '%u' complete -T -P "$PREFIX" -D "file type (dereference symbolic link)" '%Y' complete -T -P "$PREFIX" -D "file type" '%y' complete -T -P "$PREFIX" -D "SELinux context" '%Z' complete -T -P "$PREFIX" -D "%" '%%' ;; #<<# (*%[ACT]) PREFIX=${TARGETWORD%\%?} word=${TARGETWORD#"$PREFIX"} #>># complete -T -P "$PREFIX" -D "day of week, full, localized" "${word}A" complete -T -P "$PREFIX" -D "day of week, short, localized" "${word}a" complete -T -P "$PREFIX" -D "month, full, localized" "${word}B" complete -T -P "$PREFIX" -D "month, short, localized" "${word}b" complete -T -P "$PREFIX" -D "date and time, localized" "${word}c" complete -T -P "$PREFIX" -D "like %m/%d/%y (e.g. 12/31/99)" "${word}D" complete -T -P "$PREFIX" -D "day of month [01-31]" "${word}d" complete -T -P "$PREFIX" -D "hour [00-23]" "${word}H" complete -T -P "$PREFIX" -D "hour [01-12]" "${word}I" complete -T -P "$PREFIX" -D "day of year [001-366]" "${word}j" complete -T -P "$PREFIX" -D "hour [ 0-23] (space-padded)" "${word}k" complete -T -P "$PREFIX" -D "hour [ 1-12] (space-padded)" "${word}l" complete -T -P "$PREFIX" -D "month [01-12]" "${word}m" complete -T -P "$PREFIX" -D "AM or PM, localized" "${word}p" complete -T -P "$PREFIX" -D "12-hour clock time with AM/PM, localized" "${word}r" complete -T -P "$PREFIX" -D "second" "${word}S" complete -T -P "$PREFIX" -D "like %H:%M:%S (e.g. 13:45:56)" "${word}T" complete -T -P "$PREFIX" -D "week of year [00-53] (first Sunday in week 1)" "${word}U" complete -T -P "$PREFIX" -D "week of year [00-53] (first Monday in week 1)" "${word}W" complete -T -P "$PREFIX" -D "day of week in numeral [0-6]" "${word}w" complete -T -P "$PREFIX" -D "time, localized" "${word}X" complete -T -P "$PREFIX" -D "date, localized" "${word}x" complete -T -P "$PREFIX" -D "year, full (e.g. 1999)" "${word}Y" complete -T -P "$PREFIX" -D "year within century [00-99]" "${word}y" complete -T -P "$PREFIX" -D "timezone name" "${word}Z" complete -T -P "$PREFIX" -D "seconds since epoch" "${word}@" complete -T -P "$PREFIX" -D "the default localized format" "${word}+" ;; #<<# esac return 0 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/fold000066400000000000000000000017561354143602500165520ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "fold" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/fold { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "b ${long:+--bytes}; count bytes rather than columns" "s ${long:+--spaces}; don't break at non-blank characters" "w: ${long:+--width:}; specify the maximum line width" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "c --characters; count characters rather than columns" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gawk000066400000000000000000000002631354143602500165470ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "gawk" command. function completion/gawk { command -f completion//reexecute awk } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/getconf000066400000000000000000000140621354143602500172450ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "getconf" command. # Supports POSIX 2008, GNU libc 2.12.1, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/getconf { typeset type="$(uname 2>/dev/null)" typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "v:; specify a programming environment" ) #<# command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (v) complete -P "$PREFIX" XBS5_ILP32_OFF32 XBS5_ILP32_OFFBIG XBS5_LP64_OFF64 XBS5_LPBIG_OFFBIG complete -P "$PREFIX" POSIX_V6_ILP32_OFF32 POSIX_V6_ILP32_OFFBIG POSIX_V6_LP64_OFF64 POSIX_V6_LPBIG_OFFBIG complete -P "$PREFIX" POSIX_V7_ILP32_OFF32 POSIX_V7_ILP32_OFFBIG POSIX_V7_LP64_OFF64 POSIX_V7_LPBIG_OFFBIG ;; ('') command -f completion//getoperands case ${WORDS[#]} in (0) # fpathconf variables complete FILESIZEBITS LINK_MAX MAX_CANON MAX_INPUT NAME_MAX PATH_MAX PIPE_BUF complete POSIX2_SYMLINKS POSIX_ALLOC_SIZE_MIN POSIX_REC_INCR_XFER_SIZE POSIX_REC_MAX_XFER_SIZE POSIX_REC_MIN_XFER_SIZE POSIX_REC_XFER_ALIGN SYMLINK_MAX complete _POSIX_CHOWN_RESTRICTED _POSIX_NO_TRUNC _POSIX_VDISABLE _POSIX_ASYNC_IO _POSIX_PRIO_IO _POSIX_SYNC_IO _POSIX_TIMESTAMP_RESOLUTION # sysconf variables complete AIO_LISTIO_MAX AIO_MAX AIO_PRIO_DELTA_MAX ARG_MAX ATEXIT_MAX BC_BASE_MAX BC_DIM_MAX BC_SCALE_MAX BC_STRING_MAX CHILD_MAX COLL_WEIGHTS_MAX DELAYTIMER_MAX EXPR_NEST_MAX HOST_NAME_MAX IOV_MAX LINE_MAX LOGIN_NAME_MAX NGROUPS_MAX MQ_OPEN_MAX MQ_PRIO_MAX OPEN_MAX complete _POSIX_ADVISORY_INFO _POSIX_BARRIERS _POSIX_ASYNCHRONOUS_IO _POSIX_CLOCK_SELECTION _POSIX_CPUTIME _POSIX_FILE_LOCKING _POSIX_FSYNC _POSIX_IPV6 _POSIX_JOB_CONTROL _POSIX_MAPPED_FILES _POSIX_MEMLOCK _POSIX_MEMLOCK_RANGE _POSIX_MEMORY_PROTECTION _POSIX_MESSAGE_PASSING _POSIX_MONOTONIC_CLOCK _POSIX_MULTI_PROCESS _POSIX_PRIORITIZED_IO _POSIX_PRIORITY_SCHEDULING _POSIX_RAW_SOCKETS _POSIX_READER_WRITER_LOCKS _POSIX_REALTIME_SIGNALS _POSIX_REGEXP _POSIX_SAVED_IDS _POSIX_SEMAPHORES _POSIX_SHARED_MEMORY_OBJECTS _POSIX_SHELL _POSIX_SPAWN _POSIX_SPIN_LOCKS _POSIX_SPORADIC_SERVER _POSIX_SS_REPL_MAX _POSIX_SYMLOOP_MAX _POSIX_SYNCHRONIZED_IO _POSIX_THREAD_ATTR_STACKADDR _POSIX_THREAD_ATTR_STACKSIZE _POSIX_THREAD_CPUTIME _POSIX_THREAD_PRIO_INHERIT _POSIX_THREAD_PRIO_PROTECT _POSIX_THREAD_PRIORITY_SCHEDULING _POSIX_THREAD_PROCESS_SHARED _POSIX_THREAD_ROBUST_PRIO_INHERIT _POSIX_THREAD_ROBUST_PRIO_PROTECT _POSIX_THREAD_SAFE_FUNCTIONS _POSIX_THREAD_SPORADIC_SERVER _POSIX_THREADS complete _POSIX_TIMEOUTS _POSIX_TIMERS _POSIX_TRACE _POSIX_TRACE_EVENT_FILTER _POSIX_TRACE_EVENT_NAME_MAX _POSIX_TRACE_INHERIT _POSIX_TRACE_LOG _POSIX_TRACE_NAME_MAX _POSIX_TRACE_SYS_MAX _POSIX_TRACE_USER_EVENT_MAX _POSIX_TYPED_MEMORY_OBJECTS _POSIX_VERSION _POSIX_V7_ILP32_OFF32 _POSIX_V7_ILP32_OFFBIG _POSIX_V7_LP64_OFF64 _POSIX_V7_LPBIG_OFFBIG _POSIX_V6_ILP32_OFF32 _POSIX_V6_ILP32_OFFBIG _POSIX_V6_LP64_OFF64 _POSIX_V6_LPBIG_OFFBIG _POSIX2_C_BIND _POSIX2_C_DEV _POSIX2_C_VERSION _POSIX2_CHAR_TERM _POSIX2_FORT_DEV _POSIX2_FORT_RUN _POSIX2_LOCALEDEF _POSIX2_PBS _POSIX2_PBS_ACCOUNTING _POSIX2_PBS_CHECKPOINT _POSIX2_PBS_LOCATE _POSIX2_PBS_MESSAGE _POSIX2_PBS_TRACK _POSIX2_SW_DEV _POSIX2_UPE _POSIX2_VERSION complete PAGE_SIZE PAGESIZE PTHREAD_DESTRUCTOR_ITERATIONS PTHREAD_KEYS_MAX PTHREAD_STACK_MIN PTHREAD_THREADS_MAX RE_DUP_MAX _REGEX_VERSION RTSIG_MAX SEM_NSEMS_MAX SEM_VALUE_MAX SIGQUEUE_MAX STREAM_MAX SYMLOOP_MAX TIMER_MAX TTY_NAME_MAX TZNAME_MAX complete _XBS5_ILP32_OFF32 _XBS5_ILP32_OFFBIG _XBS5_LP64_OFF64 _XBS5_LPBIG_OFFBIG _XOPEN_CRYPT _XOPEN_ENH_I18N _XOPEN_LEGACY _XOPEN_REALTIME _XOPEN_REALTIME_THREADS _XOPEN_SHM _XOPEN_STREAMS _XOPEN_UNIX _XOPEN_UUCP _XOPEN_VERSION _XOPEN_XCU_VERSION # confstr variables complete PATH complete POSIX_V7_ILP32_OFF32_CFLAGS POSIX_V7_ILP32_OFF32_LDFLAGS POSIX_V7_ILP32_OFF32_LIBS POSIX_V7_ILP32_OFFBIG_CFLAGS POSIX_V7_ILP32_OFFBIG_LDFLAGS POSIX_V7_ILP32_OFFBIG_LIBS POSIX_V7_LP64_OFF64_CFLAGS POSIX_V7_LP64_OFF64_LDFLAGS POSIX_V7_LP64_OFF64_LIBS POSIX_V7_LPBIG_OFFBIG_CFLAGS POSIX_V7_LPBIG_OFFBIG_LDFLAGS POSIX_V7_LPBIG_OFFBIG_LIBS POSIX_V7_THREADS_CFLAGS POSIX_V7_THREADS_LDFLAGS POSIX_V7_WIDTH_RESTRICTED_ENVS V7_ENV complete POSIX_V6_ILP32_OFF32_CFLAGS POSIX_V6_ILP32_OFF32_LDFLAGS POSIX_V6_ILP32_OFF32_LIBS POSIX_V6_ILP32_OFFBIG_CFLAGS POSIX_V6_ILP32_OFFBIG_LDFLAGS POSIX_V6_ILP32_OFFBIG_LIBS POSIX_V6_LP64_OFF64_CFLAGS POSIX_V6_LP64_OFF64_LDFLAGS POSIX_V6_LP64_OFF64_LIBS POSIX_V6_LPBIG_OFFBIG_CFLAGS POSIX_V6_LPBIG_OFFBIG_LDFLAGS POSIX_V6_LPBIG_OFFBIG_LIBS POSIX_V6_WIDTH_RESTRICTED_ENVS V6_ENV complete XBS5_ILP32_OFF32_CFLAGS XBS5_ILP32_OFF32_LDFLAGS XBS5_ILP32_OFF32_LIBS XBS5_ILP32_OFF32_LINTFLAGS XBS5_ILP32_OFFBIG_CFLAGS XBS5_ILP32_OFFBIG_LDFLAGS XBS5_ILP32_OFFBIG_LIBS XBS5_ILP32_OFFBIG_LINTFLAGS XBS5_LP64_OFF64_CFLAGS XBS5_LP64_OFF64_LDFLAGS XBS5_LP64_OFF64_LIBS XBS5_LP64_OFF64_LINTFLAGS XBS5_LPBIG_OFFBIG_CFLAGS XBS5_LPBIG_OFFBIG_LDFLAGS XBS5_LPBIG_OFFBIG_LIBS XBS5_LPBIG_OFFBIG_LINTFLAGS # other limits.h variables complete _POSIX_CLOCKRES_MIN complete _POSIX_AIO_LISTIO_MAX _POSIX_AIO_MAX _POSIX_ARG_MAX _POSIX_CHILD_MAX _POSIX_DELAYTIMER_MAX _POSIX_HOST_NAME_MAX _POSIX_LINK_MAX _POSIX_LOGIN_NAME_MAX _POSIX_MAX_CANON _POSIX_MAX_INPUT _POSIX_MQ_OPEN_MAX _POSIX_MQ_PRIO_MAX _POSIX_NAME_MAX _POSIX_NGROUPS_MAX _POSIX_OPEN_MAX _POSIX_PATH_MAX _POSIX_PIPE_BUF _POSIX_RE_DUP_MAX _POSIX_RTSIG_MAX _POSIX_SEM_NSEMS_MAX _POSIX_SEM_VALUE_MAX _POSIX_SIGQUEUE_MAX _POSIX_SSIZE_MAX _POSIX_SS_REPL_MAX _POSIX_STREAM_MAX _POSIX_SS_REPL_MAX _POSIX_SYMLINK_MAX _POSIX_SYMLOOP_MAX _POSIX_THREAD_DESTRUCTOR_ITERATIONS _POSIX_THREAD_KEYS_MAX _POSIX_THREAD_THREADS_MAX _POSIX_TIMER_MAX _POSIX_TRACE_EVENT_NAME_MAX _POSIX_TRACE_NAME_MAX _POSIX_TRACE_SYS_MAX _POSIX_TRACE_USER_EVENT_MAX _POSIX_TTY_NAME_MAX _POSIX_TZNAME_MAX _POSIX2_BC_BASE_MAX _POSIX2_BC_DIM_MAX _POSIX2_BC_SCALE_MAX _POSIX2_BC_STRING_MAX _POSIX2_CHARCLASS_NAME_MAX _POSIX2_COLL_WEIGHTS_MAX _POSIX2_EXPR_NEST_MAX _POSIX2_LINE_MAX _POSIX2_RE_DUP_MAX _XOPEN_IOV_MAX _XOPEN_NAME_MAX _XOPEN_PATH_MAX ;; (1) complete -f ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/getopts000066400000000000000000000007361354143602500173100ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "getopts" built-in command. function completion/getopts { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -eq 1 ]; then complete -v elif [ ${WORDS[#]} -gt 1 ]; then complete -f fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gex000066400000000000000000000003001354143602500163710ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "gex" command. # Supports Vim 7.3. function completion/gex { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git000066400000000000000000000641711354143602500164110ustar00rootroot00000000000000# (C) 2011-2019 magicant # Completion script for the "git" command. # Supports Git 2.9.2. function completion/git { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "C:; specify a directory to operate in" "c:; specify a configuration parameter" "--bare; treat the repository as a bare repository" "--exec-path::; specify or print the directory containing core git executables" "--git-dir:; specify the repository directory" "--glob-pathspecs; enable globbing on all path-specs" "--html-path; print the directory where HTML documentation is installed" "--icase-pathspecs; treat path-specs case-insensitively" "--info-path; print the directory where info manuals are installed" "--literal-pathspecs; treat path-specs literally" "--man-path; print the directory where manual pages are installed" "--namespace:; specify a namespace" "--noglob-pathspecs; disable globbing on all path-specs" "p --paginate; run a pager to view Git's output" "--no-pager; don't run a pager to view Git's output" "--no-replace-objects; don't use replacement refs" "--work-tree:; specify the working tree directory" "--help" "--version" ) #<# # convert "--help" to "help" typeset i=2 while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (-c|--git-dir|--work-tree) i=$((i+1)) ;; (--help) WORDS=("${WORDS[1,i-1]}" "help" "${WORDS[i+1,-1]}") ;; (-?*) ;; (*) break ;; esac i=$((i+1)) done command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (c) if command -vf completion//git::completeoptionname >/dev/null 2>&1 || . -AL completion/git-config; then command -f completion//git::completeoptionname = fi ;; (C|--exec-path|--git-dir|--work-tree) complete -P "$PREFIX" -S / -T -d ;; ('') # find first non-option argument and # parse some global options typeset OPTIND=2 gitcmd= while [ $OPTIND -le ${WORDS[#]} ]; do case ${WORDS[OPTIND]} in (-c) OPTIND=$((OPTIND+1)) ;; (--git-dir) OPTIND=$((OPTIND+1)) typeset -x GIT_DIR="${WORDS[OPTIND]}" ;; (--git-dir=*) typeset -x GIT_DIR="${WORDS[OPTIND]#*=}" ;; (--work-tree) OPTIND=$((OPTIND+1)) typeset -x GIT_WORK_TREE="${WORDS[OPTIND]}" ;; (--work-tree=*) typeset -x GIT_WORK_TREE="${WORDS[OPTIND]#*=}" ;; (-?*) ;; (*) gitcmd=${WORDS[OPTIND]} break ;; esac OPTIND=$((OPTIND+1)) done if [ $OPTIND -le ${WORDS[#]} ]; then # resolve command alias typeset alias if alias="$(git config --get alias."$gitcmd")" 2>/dev/null; then case $alias in (!*) WORDS=(${alias#!} "${WORDS[OPTIND+1,-1]}") command -f completion//reexecute return ;; (?*) WORDS=("${WORDS[1,OPTIND-1]}" \ $alias "${WORDS[OPTIND+1,-1]}") gitcmd=${alias%%[[:space:]]*} ;; esac fi OPTIND=$((OPTIND+1)) # complete command argument typeset OLDWORDS OLDWORDS=("$WORDS") WORDS=("${WORDS[1]}" "${WORDS[OPTIND,-1]}") if [ ${WORDS[#]} -le 1 ]; then case $TARGETWORD in (-*) complete -- --help esac fi if { command -vf "completion/git::$gitcmd:arg" || . -AL "completion/git-$gitcmd"; } >/dev/null 2>&1; then command -f "completion/git::$gitcmd:arg" else complete -P "$PREFIX" -f fi else # complete command name command -f completion/git::completecmd command -f completion/git::completealias fi ;; esac } function completion/git::completecmd { #>># complete -P "$PREFIX" -D "add files to the index" add complete -P "$PREFIX" -D "apply patches from a mailbox" am complete -P "$PREFIX" -D "show a file with commit info" annotate blame complete -P "$PREFIX" -D "apply patches" apply complete -P "$PREFIX" -D "import an Arch repository" archimport complete -P "$PREFIX" -D "create an archive of file tree" archive complete -P "$PREFIX" -D "binary-search for a change that introduced a bug" bisect complete -P "$PREFIX" -D "list, create, or remove branches" branch complete -P "$PREFIX" -D "move objects and refs by archive" bundle complete -P "$PREFIX" -D "print info about object or files" cat-file # discouraged: complete -P "$PREFIX" -D "" check-attr # discouraged: complete -P "$PREFIX" -D "" check-ref-format complete -P "$PREFIX" -D "check out a branch to the working tree" checkout complete -P "$PREFIX" -D "copy files from the index to the working tree" checkout-index complete -P "$PREFIX" -D "list common commits between branches" cherry complete -P "$PREFIX" -D "apply the changes in existing commits" cherry-pick complete -P "$PREFIX" -D "commit using a GUI tool" citool complete -P "$PREFIX" -D "remove untracked files from the working tree" clean complete -P "$PREFIX" -D "copy a repository into a new directory" clone complete -P "$PREFIX" -D "record changes to the repository" commit complete -P "$PREFIX" -D "create a new commit object" commit-tree complete -P "$PREFIX" -D "show or set options" config complete -P "$PREFIX" -D "count unpacked objects and its disk consumption" count-objects complete -P "$PREFIX" -D "export a commit to a CVS checkout" cvsexportcommit complete -P "$PREFIX" -D "import a CVS repository" cvsimport complete -P "$PREFIX" -D "emulate a CVS server" cvsserver complete -P "$PREFIX" -D "simple TCP server" daemon complete -P "$PREFIX" -D "show the most recent tag reachable from a commit" describe complete -P "$PREFIX" -D "show differences between commits and files" diff complete -P "$PREFIX" -D "compare files in the index and working tree" diff-files complete -P "$PREFIX" -D "compare files in the index and a commit" diff-index complete -P "$PREFIX" -D "compare files in two tree object" diff-tree complete -P "$PREFIX" -D "run tools to view commit diff" difftool complete -P "$PREFIX" -D "dump objects in a text format" fast-export complete -P "$PREFIX" -D "import objects" fast-import complete -P "$PREFIX" -D "obtain objects and refs from another repository" fetch complete -P "$PREFIX" -D "fetch missing objects from another repository" fetch-pack complete -P "$PREFIX" -D "rewrite branches using shell commands" filter-branch # discouraged: complete -P "$PREFIX" -D "" fmt-merge-msg complete -P "$PREFIX" -D "run a command for each ref" for-each-ref complete -P "$PREFIX" -D "prepare patches for email submission" format-patch complete -P "$PREFIX" -D "verify integrity of the repository database" fsck complete -P "$PREFIX" -D "clean up and optimize the repository" gc complete -P "$PREFIX" -D "show the commit ID of a tar archive" get-tar-commit-id complete -P "$PREFIX" -D "search files using regular expressions" grep complete -P "$PREFIX" -D "GUI front end" gui complete -P "$PREFIX" -D "compute object IDs for files" hash-object complete -P "$PREFIX" -D "show help" help complete -P "$PREFIX" -D "server-side program for Git over HTTP" http-backend # discouraged: complete -P "$PREFIX" -D "" http-fetch # discouraged: complete -P "$PREFIX" -D "" http-push complete -P "$PREFIX" -D "send patches to an IMAP folder" imap-send complete -P "$PREFIX" -D "build a pack index file" index-pack complete -P "$PREFIX" -D "make a new empty repository" init complete -P "$PREFIX" -D "set up gitweb for browsing the repository" instaweb complete -P "$PREFIX" -D "show commit logs" log # deprecated: complete -P "$PREFIX" -D "recover lost refs" lost-found complete -P "$PREFIX" -D "show info on files in the index and working tree" ls-files complete -P "$PREFIX" -D "list remote refs" ls-remote complete -P "$PREFIX" -D "list files in a tree object" ls-tree # discouraged: complete -P "$PREFIX" -D "" mailinfo # discouraged: complete -P "$PREFIX" -D "" mailsplit complete -P "$PREFIX" -D "merge branches" merge complete -P "$PREFIX" -D "find a good common ancestor for a merge" merge-base complete -P "$PREFIX" -D "perform 3-way merge" merge-file complete -P "$PREFIX" -D "run a merge program" merge-index # discouraged: complete -P "$PREFIX" -D "" merge-one-file complete -P "$PREFIX" -D "show results of a 3-way merge" merge-tree complete -P "$PREFIX" -D "run tools to resolve merge conflicts" mergetool complete -P "$PREFIX" -D "create a tag object" mktag complete -P "$PREFIX" -D "create a tree object" mktree complete -P "$PREFIX" -D "move files" mv complete -P "$PREFIX" -D "show a symbolic name for a commit" name-rev complete -P "$PREFIX" -D "manipulate object notes" notes complete -P "$PREFIX" -D "pack objects into an archive" pack-objects complete -P "$PREFIX" -D "find redundant packs" pack-redundant complete -P "$PREFIX" -D "pack refs into a single file for performance" pack-refs # discouraged: complete -P "$PREFIX" -D "" parse-remote # discouraged: complete -P "$PREFIX" -D "" patch-id # deprecated: complete -P "$PREFIX" -D "" peek-remote # discouraged: complete -P "$PREFIX" -D "" prune complete -P "$PREFIX" -D "remove redundant objects that are already packed" prune-packed complete -P "$PREFIX" -D "fetch and merge remote branches" pull complete -P "$PREFIX" -D "send objects and update remote refs" push complete -P "$PREFIX" -D "apply a quilt patch set" quiltimport complete -P "$PREFIX" -D "read a tree into the index" read-tree complete -P "$PREFIX" -D "change the branching point of a branch" rebase # discouraged: complete -P "$PREFIX" -D "" receive-pack complete -P "$PREFIX" -D "manipulate logs of revs" reflog complete -P "$PREFIX" -D "hard-link common objects in local repositories" relink complete -P "$PREFIX" -D "manage remote repository settings" remote complete -P "$PREFIX" -D "pack unpacked objects" repack complete -P "$PREFIX" -D "manipulate object replacements" replace # deprecated: complete -P "$PREFIX" -D "" repo-config complete -P "$PREFIX" -D "print a message that helps a pull request" request-pull # discouraged: complete -P "$PREFIX" -D "" rerere complete -P "$PREFIX" -D "restore the index and working tree" reset complete -P "$PREFIX" -D "list commit objects" rev-list complete -P "$PREFIX" -D "parse git command arguments" rev-parse complete -P "$PREFIX" -D "undo some commits" revert complete -P "$PREFIX" -D "remove files" rm complete -P "$PREFIX" -D "send patches as emails" send-email complete -P "$PREFIX" -D "send objects to another repository" send-pack # discouraged: complete -P "$PREFIX" -D "" sh-setup # discouraged: complete -P "$PREFIX" -D "" shell complete -P "$PREFIX" -D "print a summary of commit logs" shortlog complete -P "$PREFIX" -D "show objects" show complete -P "$PREFIX" -D "list branches and their commits" show-branch complete -P "$PREFIX" -D "show packed archive index" show-index complete -P "$PREFIX" -D "list refs in the repository" show-ref complete -P "$PREFIX" -D "manipulate stashes" stash complete -P "$PREFIX" -D "show the status of the working tree" status # discouraged: complete -P "$PREFIX" -D "" stripspace complete -P "$PREFIX" -D "manipulate submodules" submodule complete -P "$PREFIX" -D "operate with a Subversion repository" svn complete -P "$PREFIX" -D "make or resolve a symbolic ref" symbolic-ref complete -P "$PREFIX" -D "manipulate tags" tag # deprecated: complete -P "$PREFIX" -D "" tar-tree complete -P "$PREFIX" -D "create a temporary file for a blob" unpack-file complete -P "$PREFIX" -D "unpack objects from an archive" unpack-objects complete -P "$PREFIX" -D "register file contents in the working tree to the index" update-index complete -P "$PREFIX" -D "modify a ref's target safely" update-ref complete -P "$PREFIX" -D "update auxiliary files for dumb servers" update-server-info # discouraged: complete -P "$PREFIX" -D "" upload-archive # discouraged: complete -P "$PREFIX" -D "" upload-pack complete -P "$PREFIX" -D "show a git logical variable" var complete -P "$PREFIX" -D "check integrity of packed objects" verify-pack complete -P "$PREFIX" -D "check the GPG signature of tags" verify-tag complete -P "$PREFIX" -D "show logs and differences between commits" whatchanged complete -P "$PREFIX" -D "manage additional working trees" worktree complete -P "$PREFIX" -D "create a tree object from the index" write-tree #<<# typeset cmd while read -r cmd; do cmd=${cmd##*/} complete -P "$PREFIX" -- "${cmd#git-}" done 2>/dev/null <(command -f completion//allcommands 'git-*') } function completion/git::completealias { typeset usesuffix= suffix if [ $# -gt 0 ]; then usesuffix=true suffix=$1 fi typeset name value while read -r name value; do complete -P "$PREFIX" -D "= $value" ${usesuffix:+-S "$suffix" -T} \ -- "${name#alias.}" done 2>/dev/null <(git config --get-regexp 'alias\..*') } function completion/git::completeref { typeset abbrprefixes completefull=true range usesuffix= suffix abbrprefixes=(refs/ refs/tags/ refs/heads/ refs/remotes/) while [ $# -gt 0 ]; do case $1 in (abbrprefixes=*) abbrprefixes=(${1#abbrprefixes=}) ;; (dontcompletefull=*) completefull= ;; (suffix=*) usesuffix=true suffix=${1#suffix=} ;; (range=*) typeset word="${TARGETWORD#"$PREFIX"}" case $word in (-*) complete -P "$PREFIX" -D "exclude following commits" -- --not esac word=${word##*..} typeset PREFIX="${TARGETWORD%"$word"}" ;; (*) break ;; esac shift done typeset targetword="${TARGETWORD#"$PREFIX"}" if [ $# -eq 0 ]; then complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -A '*HEAD' \ -- $( (ls -- "$(git rev-parse --git-dir)") 2>/dev/null) set -- --all # complete commit ID if grep -Eq '^[[:xdigit:]]*$' <<<"$targetword" 2>/dev/null; then typeset id message while read -r id message; do complete -P "$PREFIX" -D "$message" \ ${usesuffix:+-S "$suffix" -T} -- "$id" done 2>/dev/null \ <(git rev-list --all --oneline --date-order | grep "^$targetword" | head) fi fi # complete symbolic ref typeset fullref ref abbr typeset word="${targetword##*/}" typeset prefix="${targetword%"$word"}" while read -r fullref; do for abbr in ${completefull:+""} "$abbrprefixes"; do ref=${fullref#"$abbr"} case $ref in ("$targetword"*) ref="${ref#"$prefix"}" case $ref in (*/*) complete -P "$PREFIX$prefix" -S / -T -- "${ref%%/*}" ;; (*) complete -P "$PREFIX$prefix" \ ${usesuffix:+-S "$suffix" -T} -- "$ref" ;; esac esac done done 2>/dev/null <(git rev-parse --symbolic "$@") } # $1 = remote name function completion/git::completeremoteref { typeset word="${TARGETWORD#"$PREFIX"}" case $word in (refs/heads/*) word=${word#refs/heads/} PREFIX=${TARGETWORD%"$word"} ;; (*) # We don't have info about remote non-branch refs, # so complete local refs for tags. "--branches" is # specified so that "refs/heads/" can be a candidate. command -f completion/git::completeref \ abbrprefixes= --symbolic-full-name \ --branches --tags ;; esac # complete remote tracking branches command -f completion/git::completeref \ dontcompletefull=true abbrprefixes="refs/remotes/$1/" \ --symbolic-full-name --remotes="$1" } function completion/git::completepath { if ! git rev-parse --git-dir >/dev/null 2>&1; then return 1 fi typeset opt OPTIND=1 type=auto while getopts ar opt; do case $opt in (a) type=abs ;; (r) type=rel ;; esac done shift $((OPTIND-1)) typeset tree="${1-HEAD}" targetpath="${TARGETWORD#"$PREFIX"}" lspath prefixpath if [ "$type" = auto ]; then case $targetpath in (./*|../*) type=rel ;; (*) type=abs ;; esac fi case $type in (abs) lspath=${targetpath} ;; (rel) lspath=$(git rev-parse --show-prefix)${targetpath} ;; esac case $lspath in (*/*) lspath=${lspath%/*}/ ;; (*) lspath= ;; esac case $targetpath in (*/*) prefixpath=${targetpath%/*}/ ;; (*) prefixpath= ;; esac typeset PREFIX="$PREFIX$prefixpath" typeset mode type id path while read -r mode type id path; do case $type in (tree) opt='-S / -T' ;; (*) opt= ;; esac complete -P "$PREFIX" $opt -- "${path##*/}" done 2>/dev/null <(git ls-tree --full-tree "$tree" -- "${lspath:-.}") } function completion/git::completerefpath { typeset i=2 hyphenhyphen=false treeish=HEAD while [ $i -le ${WORDS[#]} ]; do case "${WORDS[i]}" in (--) hyphenhyphen=true break ;; (-*) ;; (*) treeish=${WORDS[i]} ;; esac i=$((i+1)) done if ! $hyphenhyphen; then command -f completion/git::completeref "$@" fi if ! git show "$treeish^{tree}" >/dev/null 2>&1; then treeish=HEAD fi command -f completion/git::completepath -r "$treeish" } # $1 = regex to select paths # $2... = options for "git status" # not supporting $PREFIX function completion/git::completefilteredpath { typeset dir="$(dirname -- "${TARGETWORD}X")" if ! ( typeset CDPATH= && d1=$( git rev-parse --show-toplevel) && d2=$(cd -P -- "$dir" && git rev-parse --show-toplevel) && test "$d1" = "$d2") 2>/dev/null then # don't complete paths in submodule return fi typeset pathprefix="$({ typeset CDPATH= && cd -P -- "$dir" && git rev-parse --show-prefix } 2>/dev/null)" typeset prefix="${TARGETWORD%"${TARGETWORD##*/}"}" typeset file while read -r file; do file=${file#"$pathprefix"} case $file in (*/*) complete -P "$prefix" -S / -T -- "${file%%/*}" ;; (*) complete -P "$prefix" -- "$file" ;; esac done 2>/dev/null <( if [ "$#" -eq 0 ]; then git diff --name-only HEAD \ ${pathprefix:+-- "$pathprefix"} else git status --porcelain "${@[2,-1]}" -- "$dir/" | grep "${1-}" | cut -c 4- | sed 's/^.* -> //' fi ) } function completion/git::completeobject { typeset targetword="${TARGETWORD#"$PREFIX"}" typeset t="$(sed 's;[@^]{[^}]*};;g' <<<"$targetword")" case $t in (*:*) typeset path="${t#*:}" typeset treeish="${targetword%":$path"}" typeset PREFIX="$PREFIX$treeish:" command -f completion/git::completepath "${treeish:-HEAD}" # XXX empty treeish should fall back to the current index rather than HEAD ;; (*) command -f completion/git::completeref ;; esac } function completion/git::completeremote { # XXX should complete on the basis of git-config typeset remote while read -r remote; do complete -P "$PREFIX" -- "$remote" # XXX description done 2>/dev/null <( gitdir=$(git rev-parse --git-dir) || exit git remote typeset CDPATH= (cd -P -- "$gitdir/remotes" && ls -A) (cd -P -- "$gitdir/branches" && ls -A) ) } function completion/git::getorderopts { OPTIONS=("$OPTIONS" #># "--author-date-order; sort commits by author date while keeping children before parent" "--date-order; sort commits by commit date while keeping children before parent" "--topo-order; show in topological order" ) #<# } function completion/git::getprettyopts { OPTIONS=("$OPTIONS" #># "--abbrev-commit; abbreviate commit IDs" "--encoding::; specify an output encoding" "--format:; specify an output format" "--no-abbrev-commit; print full commit IDs" "--no-notes --no-standard-notes; don't print notes" "--notes:: --standard-notes --show-notes::; print notes" "--oneline; like --format=oneline --abbrev-commit" "--pretty::; print in a human-friendly format" ) #<# } function completion/git::completeprettyopts { case $ARGOPT in (--encoding) #TODO ;; (--format|--pretty) command -f completion/git::--format:arg ;; (--notes|--standard-notes|--show-notes) command -f completion/git::completeref abbrprefixes=refs/notes/ --glob=refs/notes ;; (*) return 1 ;; esac } function completion/git::getrefselectopts { OPTIONS=("$OPTIONS" #># "--all; print all commits reachable from any refs" "--branches::; print all (or specified) branches" "--exclude:; ignore refs that match the specified pattern" "--glob:; show refs that match the specified pattern" "--remotes::; print all (or specified) remote refs" "--tags::; print all (or specified) tags" ) #<# } function completion/git::completerefselectopts case $ARGOPT in (--branches) command -f completion/git::completeref --branches ;; (--exclude|--glob) command -f completion/git::completeref ;; (--remotes) command -f completion/git::completeref --remotes ;; (--tags) command -f completion/git::completeref --tags ;; (*) return 1 ;; esac function completion/git::--author:arg { typeset author while read -r author; do complete -P "$PREFIX" -- "$author" done 2>/dev/null <(git log --all --format=format:%an | uniq) } function completion/git::--color:arg { #>># complete -P "$PREFIX" -D "always print in color" always complete -P "$PREFIX" -D "print in color if output is terminal" auto complete -P "$PREFIX" -D "don't print in color" never } #<<# function completion/git::--column:arg { #>># complete -P "$PREFIX" -D "always print in columns" always complete -P "$PREFIX" -D "print in columns if output is terminal" auto complete -P "$PREFIX" -D "fill columns before rows" column complete -P "$PREFIX" -D "make columns as narrow as possible" dense complete -P "$PREFIX" -D "don't print in columns" never complete -P "$PREFIX" -D "keep all columns same-sized" nodense complete -P "$PREFIX" -D "print in one column" plain complete -P "$PREFIX" -D "fill rows before columns" row } #<<# function completion/git::--date:arg { #>># complete -P "$PREFIX" -D "print relative time like \"2 hours ago\"" relative complete -P "$PREFIX" -D "print in original timezone" default complete -P "$PREFIX" -D "print in customized ISO 8601 format" iso8601 complete -P "$PREFIX" -D "print in strict ISO 8601 format" iso8601-strict complete -P "$PREFIX" -D "print in RFC 2822 format" rfc2822 complete -P "$PREFIX" -D "print in YYYY-MM-DD format" short complete -P "$PREFIX" -D "print the raw timestamp value" raw complete -P "$PREFIX" local relative-local default-local iso8601-local \ iso8601-strict-local rfc2822-local short-local raw-local complete -P "$PREFIX" -D "specify a format" -T format: } #<<# # TODO --date=format:...% function completion/git::--format:arg { typeset word="${TARGETWORD#"$PREFIX"}" word=${word//%%} case $word in (format:*%*|tformat:*%*) word=%${word##*%} typeset PREFIX="${TARGETWORD%"$word"}" #>># complete -T -P "$PREFIX" -D "full commit ID" '%H' complete -T -P "$PREFIX" -D "abbreviated commit ID" '%h' complete -T -P "$PREFIX" -D "full tree ID" '%T' complete -T -P "$PREFIX" -D "abbreviated tree ID" '%t' complete -T -P "$PREFIX" -D "full parent IDs" '%P' complete -T -P "$PREFIX" -D "abbreviated parent IDs" '%p' complete -T -P "$PREFIX" -D "author name respecting .mailmap" '%aN' complete -T -P "$PREFIX" -D "author name" '%an' complete -T -P "$PREFIX" -D "author email respecting .mailmap" '%aE' complete -T -P "$PREFIX" -D "author email" '%ae' complete -T -P "$PREFIX" -D "author date in the RFC 2822 format" '%aD' complete -T -P "$PREFIX" -D "author date (relative)" '%ar' complete -T -P "$PREFIX" -D "author timestamp" '%at' complete -T -P "$PREFIX" -D "author date in the ISO 8601 format" '%ai' complete -T -P "$PREFIX" -D "committer name respecting .mailmap" '%cN' complete -T -P "$PREFIX" -D "committer name" '%cn' complete -T -P "$PREFIX" -D "committer email respecting .mailmap" '%cE' complete -T -P "$PREFIX" -D "committer email" '%ce' complete -T -P "$PREFIX" -D "committer date in the RFC 2822 format" '%cD' complete -T -P "$PREFIX" -D "committer date (relative)" '%cr' complete -T -P "$PREFIX" -D "committer timestamp" '%ct' complete -T -P "$PREFIX" -D "committer date in the ISO 8601 format" '%ci' complete -T -P "$PREFIX" -D "encoding" '%e' complete -T -P "$PREFIX" -D "subject" '%s' complete -T -P "$PREFIX" -D "subject (suitable for a filename)" '%f' complete -T -P "$PREFIX" -D "body" '%b' complete -T -P "$PREFIX" -D "raw body" '%B' complete -T -P "$PREFIX" -D "commit notes" '%N' complete -T -P "$PREFIX" -D "full reflog selector" '%gD' complete -T -P "$PREFIX" -D "abbreviated reflog selector" '%gd' complete -T -P "$PREFIX" -D "reflog subject" '%gs' complete -T -P "$PREFIX" -D "switch color to red" '%Cred' complete -T -P "$PREFIX" -D "switch color to green" '%Cgreen' complete -T -P "$PREFIX" -D "switch color to blue" '%Cblue' complete -T -P "$PREFIX" -D "reset color" '%Creset' complete -T -P "$PREFIX" -D "left, right, or boundary mark" '%m' complete -T -P "$PREFIX" -D "newline" '%n' # complete -T -P "$PREFIX" -D "" '%x' complete -T -P "$PREFIX" -D "enable line wrapping" '%w' complete -T -P "$PREFIX" -D "%" '%%' ;; #<<# (*:*) ;; (*) #>># complete -P "$PREFIX" -D "commit ID and title" oneline complete -P "$PREFIX" -D "commit ID, author, and title" short complete -P "$PREFIX" -D "commit ID, author, date, and full log message" medium complete -P "$PREFIX" -D "commit ID, author, committer, and full log message" full complete -P "$PREFIX" -D "commit ID, author, date, committer, date, and full log message" fuller complete -P "$PREFIX" -D "imitate email" email complete -P "$PREFIX" -D "raw commit object" raw complete -P "$PREFIX" -D "specify a format (newline-separated)" -T format: complete -P "$PREFIX" -D "specify a format (newline-terminated)" -T tformat: #<<# typeset name value while read -r name value; do complete -P "$PREFIX" -D "= $value" -- "${name#pretty.}" done 2>/dev/null <(git config --get-regexp 'pretty\..*') esac } function completion/git::--ignore-submodules:arg { #>># complete -P "$PREFIX" -D "ignore all changes in submodules" all complete -P "$PREFIX" -D "ignore uncommitted changes in submodules" dirty complete -P "$PREFIX" -D "show all changes in submodules" none complete -P "$PREFIX" -D "ignore untracked files in submodules" untracked } #<<# function completion/git::--pretty:arg { command -f completion/git::--format:arg "$@" } function completion/git::--receive-pack:arg { complete -P "$PREFIX" -S / -T -d # XXX should complete available remote commands } function completion/git::--recurse-submodules:arg { #>># complete -P "$PREFIX" no complete -P "$PREFIX" -D "update a submodule when the superproject has been changed" on-demand complete -P "$PREFIX" yes } #<<# function completion/git::--untracked-files:arg { #>># complete -P "$PREFIX" -D "print all individual files in untracked directories" all complete -P "$PREFIX" -D "print untracked files and directories" normal complete -P "$PREFIX" -D "don't print untracked files" no } #<<# function completion/git::--upload-pack:arg { complete -P "$PREFIX" -S / -T -d # XXX should complete available remote commands } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-add000066400000000000000000000026731354143602500171360ustar00rootroot00000000000000# (C) 2011-2013 magicant # Completion script for the "git-add" command. # Supports Git 1.7.7. function completion/git-add { WORDS=(git add "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::add:arg { OPTIONS=( #># "A --all; add all files including untracked files" "n --dry-run; don't actually add files" "e --edit; edit patch hunks before adding" "f --force; add ignored files" "--ignore-errors; continue adding other files on an error" "--ignore-missing; ignore missing files (with -n)" "N --intent-to-add; add filepaths but not their contents" "i --interactive; enter the interactive mode" "p --patch; interactively choose patch hunks to add" "--refresh; refresh stat info in the index without adding" "u --update; add tracked files only; don't add new files" "v --verbose; print affected filenames" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::add:opr ;; esac } # only complete files that can be added function completion/git::add:opr { typeset force= i=2 while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) break;; (--force) force=true; break;; (--*) ;; (-*f*) force=true; break;; esac i=$((i+1)) done command -f completion/git::completefilteredpath '^.[^ ]' \ --ignore-submodules=dirty ${force:+--ignored} } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-am000066400000000000000000000032221354143602500167720ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-am" command. # Supports Git 1.7.7. function completion/git-am { WORDS=(git am "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::am:arg { OPTIONS=( #># "3 --3way; try 3-way merge on conflict" "--abort; abort patching and restore the original branch" "--committer-date-is-author-date; use author date for committer date" "r --continue --resolved; commit the current index and continue patching" "--ignore-date; use committer date for author date" "i --interactive" "k --keep; use the mail subject intact as the commit message" "--keep-cr; don't remove carriage returns at the end of lines" "--no-keep-cr; remove carriage returns at the end of lines" "--no-scissors; cancels the --scissors OPTIONS" "--no-utf8; don't convert character encoding" "q --quiet; print error messages only" "--resolvemsg:" # not for command line use "c --scissors; remove lines before a scissors line" "s --signoff; add a \"signed-off-by\" line to the message" "--skip; skip the current patch (when restarting an aborted patch)" "u --utf8; re-encode the log message into UTF-8" ) #<# if command -vf completion/git::apply:getopt >/dev/null 2>&1 || . -AL completion/git-apply; then command -f completion/git::apply:getopt am fi command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -P "$PREFIX" -f ;; (*) if command -vf completion/git::apply:compopt >/dev/null 2>&1 || . -AL completion/git-apply; then command -f completion/git::apply:compopt fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-annotate000066400000000000000000000014041354143602500202060ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-annotate" command. # Supports Git 1.7.7. function completion/git-annotate { WORDS=(git annotate "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::annotate:arg { OPTIONS=() if command -vf completion/git::blame:getopt >/dev/null 2>&1 || . -AL completion/git-blame; then command -f completion/git::blame:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') if [ ${WORDS[#]} -le 1 ]; then command -f completion/git::completepath else command -f completion/git::completeref fi ;; (*) command -f completion/git::blame:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-apply000066400000000000000000000055351354143602500175330ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-apply" command. # Supports Git 1.7.7. function completion/git-apply { WORDS=(git apply "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::apply:arg { OPTIONS=( #># "--allow-binary-replacement --binary" "--apply; force to apply patches" "--build-fake-ancestor:" # TODO "--cached; apply patches to the index without modifying the working tree" "--check; check if patches can be applied without errors instead of applying patches" "--exclude:; skip files whose names match the specified pattern" "--inaccurate-eof; work around diff errors about missing newlines at end of file" "--include:; apply to only files whose names match the specified pattern" "--index; apply patches to the index as well as the working tree" "--no-add; apply deletions but not additions" "--recount; ignore line counts in hunk headers" "R --reverse; patch in reverse" "--stat; print diffstat instead of applying patches" "--numstat; print a diffstat in the machine-friendly format instead of applying patches" "--summary; print summary instead of applying patches" "--unidiff-zero; accept unified diffs without context lines" "v --verbose; report progress in detail" "z; print a null byte after each filename" ) #<# command -f completion/git::apply:getopt apply command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (''|--exclude|--include) complete -P "$PREFIX" -f ;; (*) command -f completion/git::apply:compopt ;; esac } function completion/git::apply:getopt { typeset apply= case $1 in (am|apply) apply=true OPTIONS=("$OPTIONS" #># "--directory:; specify a directory to work in" "p:; specify the number of pathname components to strip from file names" "--reject; apply as much as possible and leave rejected hunks in *.rej file" ) #<# esac OPTIONS=("$OPTIONS" #># "C:; specify the number of context lines in each hunk that must match" "--ignore-whitespace ${apply:+--ignore-space-change}; ignore whitespaces in context when applying patches" "--whitespace:; specify what to do with whitespace errors" ) #<# } function completion/git::apply:compopt case $ARGOPT in # ([Cp]) # ;; (--directory) complete -P "$PREFIX" -S / -T -d command -f completion/git::completepath ;; (--whitespace) command -f completion/git::--whitespace:arg ;; esac function completion/git::--whitespace:arg { #>># complete -P "$PREFIX" -D "treat as errors and print some of them" error complete -P "$PREFIX" -D "treat as errors and print all of them" error-all complete -P "$PREFIX" -D "print warnings and fix errors" fix strip complete -P "$PREFIX" -D "don't print warnings about whitespace errors" nowarn complete -P "$PREFIX" -D "print warnings but apply the patch as is" warn } #<# # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-bisect000066400000000000000000000057161354143602500176600ustar00rootroot00000000000000# (C) 2011-2016 magicant # Completion script for the "git-bisect" command. # Supports Git 2.9.2. function completion/git-bisect { WORDS=(git bisect "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::bisect:arg { typeset old new old=$(git bisect terms --term-old 2>/dev/null) || old=good new=$(git bisect terms --term-new 2>/dev/null) || new=bad if [ ${WORDS[#]} -le 1 ]; then #>># complete -P "$PREFIX" -D "mark a commit as $old" "$old" complete -P "$PREFIX" -D "mark a commit as $new" "$new" complete -P "$PREFIX" -D "print help" help complete -P "$PREFIX" -D "show the bisection log" log complete -P "$PREFIX" -D "replay a bisection log" replay complete -P "$PREFIX" -D "end bisection" reset complete -P "$PREFIX" -D "start automated bisection" run complete -P "$PREFIX" -D "mark a commit as untestable" skip complete -P "$PREFIX" -D "start bisection" start complete -P "$PREFIX" -D "show terms used in the current session" terms complete -P "$PREFIX" -D "show remaining suspects with GUI" visualize #<<# case ${TARGETWORD#"$PREFIX"} in (vie*) complete -P "$PREFIX" -D "show remaining suspects with GUI" view esac else WORDS=("${WORDS[2,-1]}") typeset subcmd="${WORDS[1]}" case $subcmd in ($old) subcmd=good ;; ($new) subcmd=bad ;; esac if command -vf "completion/git::bisect:$subcmd:arg" >/dev/null 2>&1; then command -f "completion/git::bisect:$subcmd:arg" fi fi } function completion/git::bisect:bad:arg { command -f completion/git::completeref } function completion/git::bisect:good:arg { command -f completion/git::completeref } #function completion/git::bisect:help:arg { #} #function completion/git::bisect:log:arg { #} function completion/git::bisect:replay:arg { complete -P "$PREFIX" -f } function completion/git::bisect:reset:arg { command -f completion/git::completeref } function completion/git::bisect:run:arg { WORDS=("${WORDS[2,-1]}") command -f completion//reexecute -e } function completion/git::bisect:skip:arg { command -f completion/git::completeref range=true } function completion/git::bisect:start:arg { OPTIONS=( #># "--no-checkout; don't automatically check out commits to test" "--term-old: --term-good:; specify a term for the old state" "--term-new: --term-bad:; specify a term for the new state" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--term-*) ;; ('') command -f completion/git::completerefpath ;; esac } function completion/git::bisect:terms:arg { OPTIONS=( #># "--term-old --term-good; only show the term for the old state" "--term-new --term-bad; only show the term for the new state" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--term-*) ;; esac } #function completion/git::bisect:view:arg { #} #function completion/git::bisect:visualize:arg { #} # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-blame000066400000000000000000000041151354143602500174570ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-blame" command. # Supports Git 1.7.7. function completion/git-blame { WORDS=(git blame "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::blame:arg { OPTIONS=() command -f completion/git::blame:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::blame:compopt ;; esac } function completion/git::blame:getopt { OPTIONS=("$OPTIONS" #># "--abbrev::; specify the number of commit ID digits to print" "b; show blank SHA-1 for boundary commits" "C::; detect moves and copies within a commit" "c; print in the \"git annotate\" format" "--contents:; specify a file to annotate" "--date:; specify a date format" "--encoding:; specify the encoding for commit metadata" "h; print help" "--incremental; print in a machine-friendly format" "L:; specify a line range to annotate" "l; show full commit IDs" "M::; detect moves and copies within a file" "p --porcelain; print in a machine-friendly format" "--line-porcelain; like --porcelain, but print commit info for all lines" "--reverse; search history forward instead of backward" "--root; don't treat root commits as boundary commits" "S:; specify a file containing revisions to search" "s; don't print author names and timestamps" "--score-debug; include debugging info" "e --show-email; print author emails instead of author names" "f --show-name; always print the filename" "n --show-number; print line numbers" "--show-stats; show additional stats at the end of output" "t; show times in the raw value format" "w; ignore whitespaces in comparison" ) #<# } function completion/git::blame:compopt { case $ARGOPT in # ([CLM]|--abbrev) # ;; (S|--contents) complete -P "$PREFIX" -f ;; (--*) command -vf "completion/git::$ARGOPT:arg" >/dev/null 2>&1 && command -f "completion/git::$ARGOPT:arg" ;; (*) return 1 ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-branch000066400000000000000000000046211354143602500176360ustar00rootroot00000000000000# (C) 2011-2014 magicant # Completion script for the "git-branch" command. # Supports Git 1.9.0. function completion/git-branch { WORDS=(git branch "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::branch:arg { OPTIONS=( #># "--abbrev:; specify the number of commit ID digits to print" "a --all; print local and remote-tracking branches" "--color::; show symbols in color" "--column::; columnize output" "--contains; show branches that contain the specified commit only" "l --create-reflog; enable reflog for the new branch" "D; delete a branch that has not been merged" "d --delete; delete a branch" "--edit; edit the branch explanation with an editor" "f --force; overwrite an existing branch" "--list; list branches matching the operand pattern" "M; rename a branch, overwriting an existing branch" "--merged; show branches that are contained in the specified commit only" "m --move; rename a branch" "--no-abbrev; print full commit IDs" "--no-color; like --color=never" "--no-column; print branches line by line" "--no-merged; show branches that aren't contained in the specified commit only" "--no-track; create a non-tracking branch" "q --quiet; print error messages only" "r --remotes; print or delete remote-tracking branches" "--set-upstream; like --track, but don't move HEAD" "u: --set-upstream-to:; set the upstream to the specified one" "t --track; create a remote-tracking branch" "--unset-upstream; remove the upstream" "v --verbose; print commit ID and summary for each branch" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--abbrev) ;; (--color|--column) command -f completion/git::$ARGOPT:arg ;; (u|--set-upstream-to) command -f completion/git::completeref --remotes ;; ('') typeset i=2 all=false delete=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) i=$((i+1)) break ;; (--merged|--no-merged|--contains) all=true break ;; (--*) i=$((i+1)) ;; (-*[Dd]*) delete=true break ;; (-?*) i=$((i+1)) ;; (*) break ;; esac done if ! $all && $delete || [ $i -gt ${WORDS[#]} ]; then command -f completion/git::completeref --branches else command -f completion/git::completeref fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-checkout000066400000000000000000000027161354143602500202110ustar00rootroot00000000000000# (C) 2011-2014 magicant # Completion script for the "git-checkout" command. # Supports Git 2.1.2. function completion/git-checkout { WORDS=(git checkout "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::checkout:arg { OPTIONS=( #># "B:; create or reset a new branch and check it out" "b:; create a new branch and check it out" "--conflict:; like --merge, but specify format of unmerged files" "--detach; leave HEAD in detached head state" "f --force; overwrite local changes or ignore unmerged files" "--ignore-skip-worktree-bits; ignore sparse patterns" "l; enable reflog for the new branch" "m --merge; do 3-way merge for destination branch" "--no-track; create a non-tracking branch" "--orphan:; create a new branch with no parent" "--ours; checkout local version for unmerged files" "p --patch; interactively choose hunks to check out" "q --quiet; print error and warning messages only" "--theirs; checkout remote version for unmerged files" "t --track; create a tracking branch" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ([Bb]|--orphan) command -f completion/git::completeref --branches ;; (--conflict) #>># complete -P "$PREFIX" -D "ours and theirs" merge complete -P "$PREFIX" -D "ours, theirs, and original" diff3 ;; #<<# ('') command -f completion/git::completerefpath ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-cherry000066400000000000000000000007731354143602500177010ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-cherry" command. # Supports Git 1.7.7. function completion/git-cherry { WORDS=(git cherry "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::cherry:arg { OPTIONS=( #># "v; print commit subjects" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-cherry-pick000066400000000000000000000027271354143602500206260ustar00rootroot00000000000000# (C) 2011-2013 magicant # Completion script for the "git-cherry-pick" command. # Supports Git 1.8.1.4. function completion/git-cherry-pick { WORDS=(git cherry-pick "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::cherry-pick:arg { OPTIONS=( #># "--abort; end suspended cherry-picking and restore the original state" "--allow-empty; allow commits that make no change" "--allow-empty-message" "--continue; resume suspended cherry-picking" "e --edit; reedit the message" "--ff; fast-forward if possible" "--keep-redundant-commits; don't omit already-merged commits" "m: --mainline:; apply diffs from the nth parent" "n --no-commit; don't commit the result automatically" "--quit; end suspended cherry-picking and keep the current state" "r; don't include the original commit ID in the message" "s --signoff; add a \"signed-off-by\" line to the message" "--strategy:; specify the merge strategy" "X: --strategy-option:; specify a strategy-specific option" "x; include the original commit ID in the message" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (m|--mainline) # ;; (X|--strategy*) if command -vf completion/git::merge:compopt >/dev/null 2>&1 || . -AL completion/git-merge; then command -f completion/git::merge:compopt fi ;; ('') command -f completion/git::completeref range=true ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-clean000066400000000000000000000024031354143602500174570ustar00rootroot00000000000000# (C) 2013 magicant # Completion script for the "git-clean" command. # Supports Git 1.8.1.4. function completion/git-clean { WORDS=(git clean "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::clean:arg { OPTIONS=( #># "d; remove untracked directories" "f --force; really remove untracked files" "n --dry-run; don't actually remove files, but show what would be removed" "q --quiet; print error messages only" "e: --exclude:; skip files whose names match the specified pattern" "X; remove ignored files only" "x; remove ignored files too" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (e|--exclude) complete -P "$PREFIX" -f ;; ('') : DEBUG "${WORDS}" command -f completion/git::clean:opr ;; esac } # only complete untracked files function completion/git::clean:opr { typeset arg filter='^?' for arg in "${WORDS[2,-1]}"; do case $arg in (-X) filter='^!';; (-x) filter='^[?!]';; (--) break;; esac done typeset prefix="${TARGETWORD%"${TARGETWORD##*/}"}" command -f completion/git::completefilteredpath "$filter" \ --ignore-submodules=all --ignored --untracked-files=all } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-clone000066400000000000000000000040521354143602500174770ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-clone" command. # Supports Git 1.7.7. function completion/git-clone { WORDS=(git clone "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::clone:arg { OPTIONS=( #># "--bare; clone as a bare repository" "b: --branch:; specify a branch to be the new repository's HEAD" "c: --config:; specify a config variable for the new repository" "--depth:; specify the max number of history to clone" "l --local; optimize local clone" "--mirror; clone as a mirror repository" "n --no-checkout; don't check out HEAD automatically" "--no-hardlinks; don't use hard links to spare disk space for local clone" "o: --origin:; specify a name for the source repository to set as a remote repository" "--progress; report progress" "q --quiet; don't report progress" "--recursive --recurse-submodules; initialize submodules recursively" "--reference:; specify a reference repository to share objects (possibly dangerous operation)" "--separate-git-dir:; specify a directory where the new repository is located" "s --shared; share objects in repositories (possibly dangerous operation)" "--template:; specify a directory that contains templates" "u: --upload-pack:; specify a path for git-upload-pack on the remote host" "v --verbose" # TODO description ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (b|--branch) # ;; (c|--config) if command -vf completion//git::completeoptionname >/dev/null 2>&1 || . -AL completion/git-config; then command -f completion//git::completeoptionname = fi ;; # (--depth) # ;; # (o|--origin) # ;; (u|--reference|--template|--separate-git-dir|--upload-pack) complete -P "$PREFIX" -S / -T -d ;; ('') command -f completion//getoperands command -f completion/git::clone:opr ;; esac } function completion/git::clone:opr { if [ ${WORDS[#]} -eq 0 ]; then #TODO complete remote URI fi complete -P "$PREFIX" -S / -T -d } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-commit000066400000000000000000000054671354143602500177020ustar00rootroot00000000000000# (C) 2011-2012 magicant # Completion script for the "git-commit" command. # Supports Git 1.7.7. function completion/git-commit { WORDS=(git commit "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::commit:arg { OPTIONS=( #># "a --all; include modified or deleted files that have not been added" "--allow-empty; allow a commit that makes no change" "--allow-empty-message" "--amend; redo the last commit on the current branch" "--author:; specify the author" "--cleanup:; specify the way the message is cleaned up" "--date:; specify the date" "--dry-run; don't actually commit, but show what would happen" "e --edit; reedit the message" "F: --file:; specify a file containing the message" "--fixup:; prepare the message to fix up the specified commit in later autosquash" "i --include; include operand files in the commit" "m: --message:; specify the message" "--no-edit; don't reedit the message" "--no-status; don't include file statuses in the message template" "n --no-verify; bypass the pre-commit and commit-msg hooks" "o --only; commit operand files only" "p --patch; interactive choose patch hunks to commit" "--porcelain; dry-run with the machine-friendly format" "q --quiet; suppress the commit summary message" "--reset-author; ignore the date and author of the original commit" "C: --reuse-message:; specify an existing commit from which the message is reused" "c: --reedit-message:; like -C, but reedit the message" "--short; dry-run with a short output format" "s --signoff; add a \"signed-off-by\" line to the message" "--squash:; prepare the message to squash the specified commit in later autosquash" "--status; include file statuses in the message template" "t: --template:; specify a file containing a template message to edit" "u:: --untracked-files::; print untracked files" "v --verbose; include diffs in the message template" "z; print a null byte after each filename" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--author) command -f completion/git::--author:arg ;; (--cleanup) #>># complete -P "$PREFIX" -D "like \"strip\" when editing and \"whitespace\" otherwise" default complete -P "$PREFIX" -D "delete empty lines and comments" strip complete -P "$PREFIX" -D "don't clean up the message at all" verbatim complete -P "$PREFIX" -D "delete empty lines" whitespace ;; #<<# # (--date) # TODO complete date # ;; ([Ft]|--file|--template) complete -P "$PREFIX" -f ;; ([Cc]|--fixup|--reuse-message|--reedit-message|--squash) command -f completion/git::completeref ;; (u|--untracked-files) command -f completion/git::--untracked-files:arg ;; ('') command -f completion/git::completefilteredpath ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-config000066400000000000000000000355401354143602500176520ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-config" command. # Supports Git 1.7.7. function completion/git-config { WORDS=(git config "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::config:arg { OPTIONS=( #># "--add; add a new line to the option value" "--bool; ensure the option value is a boolean" "--bool-or-int; ensure the option value is a boolean or integer" "e --edit; start an editor to edit the config file" "f: --file:; specify the config file path" "--get; print an option value" "--get-all; print one or more option values" "--get-color; print ANSI color escape sequence" "--get-colorbool; check if output should be colored" "--get-regexp; print options whose keys match the specified regular expression" "--global; get or set the global options" "--int; ensure the option value is an integer" "l --list; print all options set" "z --null; print a null byte after each key-value pair" "--path; perform tilde expansion when printing option values" "--remove-section; remove a section" "--rename-section; rename a section" "--replace-all; replace all matching values" "--system; get or set the system-wide options" "--unset; remove (part of) an option" "--unset-all; remove (all matching parts of) an option" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (f|--file) complete -P "$PREFIX" -f ;; ('') command -f completion//git::completeoptionname ;; esac } function completion//git::completeoptionname { typeset usesuffix= suffix if [ $# -gt 0 ]; then usesuffix=true suffix=$1 fi typeset word="${TARGETWORD#"$PREFIX"}" # complete existing settings typeset word2 prefix2 name value word2="${word##*.}" prefix2="${TARGETWORD%"$word2"}" while read -r name value; do name=${name#"$prefix2"} case $name in (*.*) name=${name%%.*} complete -P "$prefix2" -S . -T -- "$name" ;; (*) complete -P "$prefix2" ${usesuffix:+-S "$suffix" -T} -- \ "$name" ;; esac done 2>/dev/null <(git config --get-regexp "${word//./'\.'}.*") case $word in (add.*) PREFIX=${PREFIX}add. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ ignoreErrors ;; #<<# (advice.*) PREFIX=${PREFIX}advice. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ commitBeforeMerge detachedHead implicitIdentity\ pushNonFastForward resolveConflict statusHints ;; #<<# (alias.*) PREFIX=${PREFIX}alias. #>># command -f completion/git::completealias ${usesuffix+"$suffix"} ;; (am.*) PREFIX=${PREFIX}am. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ keepcr ;; #<<# (apply.*) PREFIX=${PREFIX}apply. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ ignorewhitespace whitespace ;; #<<# (branch.*.*) word=${word#branch.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ merge mergeoptions rebase remote ;; #<<# (branch.*) PREFIX=${PREFIX}branch. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autosetupmerge autosetuprebase #<<# command -f completion/git::completeref \ abbrprefixes=refs/heads/ dontcompletefull=true \ suffix=. --branches ;; (browser.*.*|difftool.*.*|man.*.*) word=${word#*.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ cmd path ;; #<<# (clean.*) PREFIX=${PREFIX}clean. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ requireForce ;; #<<# (color.branch.*) PREFIX=${PREFIX}color.branch. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ current local plain remote ;; #<<# (color.diff.*) PREFIX=${PREFIX}color.diff. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ commit frag func meta new old plain whitespace ;; #<<# (color.decorate.*) PREFIX=${PREFIX}color.decorate. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ branch HEAD remoteBranch stash tag ;; #<<# (color.grep.*) PREFIX=${PREFIX}color.grep. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ context filename function linenumber match \ selected separator ;; #<<# (color.interactive.*) PREFIX=${PREFIX}color.interactive. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ error header help prompt ;; #<<# (color.status.*) PREFIX=${PREFIX}color.status. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ added branch changed header nobranch untracked \ updated ;; #<<# (color.*) PREFIX=${PREFIX}color. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ branch diff grep interactive pager showbranch \ status ui #<<# complete -P "$PREFIX" -S . -T -- \ branch diff decorate grep interactive status ;; (commit.*) PREFIX=${PREFIX}commit. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ status template ;; #<<# (core.*) PREFIX=${PREFIX}core. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ abbrev askpass attributesfile autocrlf bare \ bigFileThreshold compression createObject \ deltaBaseCacheLimit editor eol excludesfile \ fileMode fsyncobjectfiles gitProxy \ ignoreCygwinFSTricks ignoreStat ignorecase \ logAllRefUpdates loosecompression notesRef \ packedGitLimit packedGitWindowSize pager \ preferSymlinkRefs preloadindex quotepath \ repositoryFormatVersion safecrlf \ sharedRepository sparseCheckout symlinks \ trustctime warnAmbiguousRefs whitespace \ worktree ;; #<<# (diff.*.*) word=${word#diff.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ binary cachetextconv command textconv \ wordregex xfuncname ;; #<<# (diff.*) PREFIX=${PREFIX}diff. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autorefreshindex dirstat external \ ignoreSubmodules mnemonicprefix noprefix \ renameLimit renames suppressBlankEmpty tool \ wordRegex ;; #<<# # (difftool.*.*) -> (browser.*.*) (difftool.*) PREFIX=${PREFIX}difftool. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ prompt ;; #<<# (fetch.*) PREFIX=${PREFIX}fetch. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ recurseSubmodules unpackLimit ;; #<<# (format.*) PREFIX=${PREFIX}format. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ attach cc numbered headers pretty signature \ signoff subjectprefix suffix thread to ;; #<<# (filter.*.*) word=${word#filter.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ clean smudge ;; #<<# (gc.*.*) word=${word#gc.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ reflogexpire reflogexpireunreachable ;; #<<# (gc.*) PREFIX=${PREFIX}gc. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ aggressiveWindow auto autopacklimit packrefs \ pruneexpire reflogexpire \ reflogexpireunreachable \ rerereresolved rerereunresolved ;; #<<# (gitcvs.*) PREFIX=${PREFIX}gitcvs. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ allbinary commitmsgannotation \ dbTableNamePrefix dbdriver dbname dbpass \ dbuser enabled logfile usecrlfattr ;; #<<# (grep.*) PREFIX=${PREFIX}grep. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ extendedRegexp lineNumber ;; #<<# (gui.*) PREFIX=${PREFIX}gui. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ blamehistoryctx commitmsgwidth \ copyblamethreshold diffcontext encoding \ fastcopyblame matchtrackingbranch \ newbranchtemplate pruneduringfetch \ spellingdictionary trustmtime ;; #<<# (guitool.*.*) word=${word#guitool.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ argropmpt cmd confirm needsfile noconsole \ norescan prompt revprompt revunmerged title ;; #<<# (help.*) PREFIX=${PREFIX}help. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autocorrect browser format ;; #<<# (http.*) PREFIX=${PREFIX}http. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ cookiefile lowSpeedLimit lowSpeedTime \ maxRequests minSessions noEPSV postBuffer \ proxy sslCAInfo sslCAPath sslCert \ sslCertPasswordProtected sslKey sslVerify \ useragent ;; #<<# (i18n.*) PREFIX=${PREFIX}i18n. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ commitEncoding logOutputEncoding ;; #<<# (imap.*) PREFIX=${PREFIX}imap. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ authMethod folder host pass port \ preformattedHTML sslverify tunnel user ;; #<<# (init.*) PREFIX=${PREFIX}init. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ templatedir ;; #<<# (instaweb.*) PREFIX=${PREFIX}instaweb. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ browser httpd local modulepath port ;; #<<# (interactive.*) PREFIX=${PREFIX}interactive. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ singlekey ;; #<<# (log.*) PREFIX=${PREFIX}log. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ abbrevCommit date decorate showroot ;; #<<# (mailmap.*) PREFIX=${PREFIX}mailmap. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ file ;; #<<# # (man.*.*) -> (browser.*.*) (man.*) PREFIX=${PREFIX}man. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ viewer ;; #<<# (merge.*.*) word=${word#merge.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ driver name recursive ;; #<<# (merge.*) PREFIX=${PREFIX}merge. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ conflictstyle defaultToUpstream ff log \ renameLimit renormalize stat tool verbosity ;; #<<# (mergetool.*.*) word=${word#mergetool.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ cmd path trustExitCode ;; #<<# (mergetool.*) PREFIX=${PREFIX}mergetool. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ keepBackup keepTemporaries prompt ;; #<<# (notes.*) PREFIX=${PREFIX}notes. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ displayRef rewriteMode rewriteRef complete -P "$PREFIX" -S . -T -- rewrite ;; #<<# (pack.*) PREFIX=${PREFIX}pack. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ compression deltaCacheLimit deltaCacheSize \ depth indexVersion packSizeLimit threads \ window windowMemory ;; #<<# (pull.*) PREFIX=${PREFIX}pull. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ octopus twohead ;; #<<# (push.*) PREFIX=${PREFIX}push. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ default ;; #<<# (rebase.*) PREFIX=${PREFIX}rebase. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autosquash stat ;; #<<# (receive.*) PREFIX=${PREFIX}receive. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autogc denyCurrentBranch denyDeleteCurrent \ denyDeletes denyNonFastForwards fsckObjects \ unpackLimit updateserverinfo ;; #<<# (remote.*.*) word=${word#remote.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ fetch mirror proxy push pushurl receivepack \ skipDefaultUpdate skipFetchAll tagopt \ uploadpack url vcs ;; #<<# (repack.*) PREFIX=${PREFIX}repack. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ usedeltabaseoffset ;; #<<# (rerere.*) PREFIX=${PREFIX}rerere. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ autoupdate enabled ;; #<<# (sendemail.*.*) word=${word#sendemail.*.} PREFIX=${TARGETWORD%"$word"} command -f completion//git::completesendemailoptionname ;; (sendemail.*) PREFIX=${PREFIX}sendemail. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ identity smtpencryption #<<# command -f completion//git::completesendemailoptionname ;; (status.*) PREFIX=${PREFIX}status. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ relativePaths showUntrackedFiles \ submodulesummary ;; #<<# (submodule.*.*) word=${word#submodule.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ fetchRecurseSubmodules ignore path update url ;; #<<# (svn-remote.*.*) word=${word#svn-remote.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ automkdirs branches commiturl fetch \ ignore-paths noMetadata pushurl rewriteRoot \ rewriteUUID tags url useSvmProps useSvnsyncprops ;; #<<# (svn.*) PREFIX=${PREFIX}svn. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ automkdirs authorsfile brokenSymlinkWorkaround \ commiturl edit findcopiesharder followparent l \ noMetadata pathnameencoding pushmergeinfo \ repack repackflags rmdir useSvmProps \ useSvnsyncprops ;; #<<# (tar.*) PREFIX=${PREFIX}tar. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ umask ;; #<<# (transfer.*) PREFIX=${PREFIX}transfer. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ unpackLimit ;; #<<# (url.*.*) word=${word#url.*.} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ insteadOf pushInsteadOf ;; #<<# (user.*) PREFIX=${PREFIX}user. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ email name signingkey ;; #<<# (web.*) PREFIX=${PREFIX}web. #>># complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ browser ;; #<<# (*) #>># complete -P "$PREFIX" -S . -T -- \ add advice alias am apply branch browser clean \ color commit core diff difftool fetch format \ filter gc gitcvs grep gui guitool help http \ i18n imap init instaweb interactive log \ mailmap man merge mergetool notes pack pager \ pretty pull push rebase receive remote remotes \ repack rerere sendemail showbranch status \ submodule svn svn-remote tar transfer url user \ web ;; #<<# esac } function completion//git::completesendemailoptionname { complete -P "$PREFIX" ${usesuffix:+-S "$suffix" -T} -- \ aliasesfile aliasfiletype bcc cc cccmd chainreplyto confirm \ envelopesender from multiedit signedoffbycc smtpdomain \ smtppass smtpserver smtpserveroption smtpserverport smtpuser \ suppresscc suppressfrom thread to validate } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-describe000066400000000000000000000021041354143602500201530ustar00rootroot00000000000000# (C) 2014 magicant # Completion script for the "git-describe" command. # Supports Git 1.9.0. function completion/git-describe { WORDS=(git describe "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::describe:arg { OPTIONS=( #># "--all; use any ref, not only an annotated tag" "--always; show uniquely abbreviated commit object as fallback" "--candidates:; specify the number of candidate tags" "--contains; use a tag that contains the described commit" "--debug; print debugging information" "--dirty::; describe HEAD with the specified suffix" "--exact-match; like --candidate=0" "--first-parent; only follow first parents of merges" "--long; always print in the long format" "--match:; specify a pattern to restrict candidate tags" "--tags; use any tag, not only an annotated tag" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (--dirty) # ;; # (--match) # ;; ('') command -f completion/git::completeref ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-diff000066400000000000000000000141371354143602500173140ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-diff" command. # Supports Git 1.7.7. function completion/git-diff { WORDS=(git diff "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::diff:arg { OPTIONS=( #># "--cached --staged; compare the index with a commit" ) #<# command -f completion/git::diff:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::diff:compopt ;; esac } function completion/git::diff:getopt { typeset diff= case $gitcmd in (diff|log|show|stash) diff=true;; esac OPTIONS=("$OPTIONS" #># "--abbrev::; abbreviate commit IDs" "--binary; print diffs for binary files" "B:: --break-rewrites::; treat a way different file as a new file" "--cc; show diffs for resolved merge conflicts only" "--check; check for whitespace errors" "--color::; show symbols in color" "--color-words::; like --word-diff=color --word-diff-regex=..." "--diff-filter:; specify categories of changes to show" "--dirstat::; print a dirstat with the specified style" "--dirstat-by-file::; print the number of changed files" "--dst-prefix:; specify a prefix for destination filenames in diffs" "--exit-code; return the exit status of 1 when there is any diffs" "--ext-diff; use an external diff program" "C:: --find-copies::; detect copies of files with the specified threshold" "--find-copies-harder; find files copied from unmodified existing files" "M:: --find-renames::; detect renames of files with the specified threshold" "--full-index; print full commit IDs in the patch format" "G:; specify a regular expression to look for in diffs" "w --ignore-all-space; ignore whitespaces in comparison" "--ignore-space-at-eol; ignore changes in whitespace at end of lines" "b --ignore-space-change; ignore changes in amount of whitespaces only" "--ignore-submodules::; ignore changes to submodules" "--inter-hunk-context:; specify the number of lines to show between hunks" "D --irreversible-delete; don't print diffs for deleted files" "l:; specify a threshold at which rename/copy detection is given up" "--name-only; just print the names of differing files" "--name-status; just print the names and statuses of differing files" "--no-color; like --color=never" "--no-ext-diff; use the internal diff program" "--no-index; compare a file with another file outside the repository" "--no-prefix; don't prefix filenames in diffs" "--no-renames; disable rename detection" "--no-textconv; don't use an external text filter to compare binary files" "--numstat; print a diffstat in the machine-friendly format" "O:; specify a file containing the order in which diffs are printed" "${diff:+p} --patch; print a patch" "--patch-with-raw; like --patch --raw" "--patch-with-stat; like --patch --stat" "--patience; generate a diff using the patience diff algorithm" "--pickaxe-all; show the whole changeset when -S/-G is specified" "--pickaxe-regex; treat the specified string as a regular expression (with -S)" "--quiet; don't print anything" "R; print reverse diffs" "--raw; print in the raw format" "--relative::; only show diffs in the specified directory" "S:; specify a string to look for in diffs" "--shortstat; print a diffstat summary" "--src-prefix:; specify a prefix for source filenames in diffs" "--stat::; print a diffstat with the specified column widths" "--stat-count:; specify the max number of files shown in the diffstat" "--stat-name-width:; specify the width of filenames in the diffstat" "--stat-width:; specify the width of the diffstat" "--submodule::; print diffs in submodules" "--summary; print summary" "a --text; assume all files are text" "--textconv; use an external text filter to compare binary files" "U:: --unified::; output in unified context format with the specified number of context lines" "u; like -U3" "--word-diff::; print word-based diffs" "--word-diff-regex:; specify a regular expression that defines a word" "z; print a null byte after each filename" ) #<# } function completion/git::diff:compopt case $ARGOPT in ([BClMU]|--abbrev|--break-rewrites|--find-*|--inter-hunk-context|--stat|--stat-*|--unified) ;; (--color|--ignore-submodules) command -f completion/git::$ARGOPT:arg ;; (--color-words|--word-diff-regex) ;; (--diff-filter) #>># complete -P "$TARGETWORD" -D "added" A complete -P "$TARGETWORD" -D "pair broken" B complete -P "$TARGETWORD" -D "copied" C complete -P "$TARGETWORD" -D "deleted" D complete -P "$TARGETWORD" -D "modified" M complete -P "$TARGETWORD" -D "renamed" R complete -P "$TARGETWORD" -D "type modified" T complete -P "$TARGETWORD" -D "unmerged" U complete -P "$TARGETWORD" -D "unknown" X complete -P "$TARGETWORD" -D "all, if any of other flag matches" '*' ;; #<<# (--dirstat|--dirstat-by-file) typeset word="${TARGETWORD#"$PREFIX"}" word=${word##*,} PREFIX=${TARGETWORD%"$word"} #>># complete -P "$PREFIX" -S , -T -D "count lines that are added or removed, but not moved" changes complete -P "$PREFIX" -S , -T -D "include changes in subdirectories when operating on the parent" cumulative complete -P "$PREFIX" -S , -T -D "count files that are changed" files complete -P "$PREFIX" -S , -T -D "count lines that appear as added or removed in diffs" lines complete -P "$PREFIX" -S , -T -D "exclude changes in subdirectories when operating on the parent" noncumulative ;; #<<# ([GS]) ;; (--relative) complete -P "$PREFIX" -S / -T -d ;; (--submodule) #>># complete -P "$PREFIX" -D "print logs for commits in submodules" log complete -P "$PREFIX" -D "just print pairs of commit IDs" short ;; #<<# (--word-diff) #>># complete -P "$PREFIX" -D "use colors" color complete -P "$PREFIX" -D "use delimiters" plain complete -P "$PREFIX" -D "print in the machine-friendly format" porcelain complete -P "$PREFIX" -D "disable word-based diffs" none ;; #<<# (O|--dst-prefix|--src-prefix) complete -P "$PREFIX" -f ;; (*) return 1 ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-diff-tree000066400000000000000000000031651354143602500202500ustar00rootroot00000000000000# (C) 2012 magicant # Completion script for the "git-diff-tree" command. # Supports Git 1.7.7. function completion/git-diff-tree { WORDS=(git diff-tree "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::diff-tree:arg { OPTIONS=() command -f completion/git::diff-tree:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -le 1 ]; then command -f completion/git::completerefpath else complete -P "$PREFIX" -f fi ;; (*) command -f completion/git::diff-tree:compopt ;; esac } function completion/git::diff-tree:getopt { OPTIONS=("$OPTIONS" #># "--always; show commit even if the diff is empty" "c; use the alternate format when printing a merge" "m; don't suppress showing diffs of merge commits" "--no-commit-id; don't print the commit ID" "r; compare directory trees recursively" "--root; compare against the null tree" "s; suppress normal diff output" "--stdin; read arguments from the standard input" "t; show tree entry itself as well as subtrees" "v; show commit messages as well" ) #<# command -f completion/git::getprettyopts { command -vf completion/git::diff:getopt >/dev/null 2>&1 || . -AL completion/git-diff; } && command -f completion/git::diff:getopt } function completion/git::diff-tree:compopt { command -f completion/git::completeprettyopts || { command -vf completion/git::diff:compopt >/dev/null 2>&1 || . -AL completion/git-diff; } && command -f completion/git::diff:compopt } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-fetch000066400000000000000000000052461354143602500174760ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-fetch" command. # Supports Git 1.7.7. function completion/git-fetch { WORDS=(git fetch "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::fetch:arg { OPTIONS=( #># "--dry-run; don't actually fetch anything" "--multiple; allow specifying multiple remotes" "p --prune; delete remote-tracking branches that no longer exist on the remote" "q --quiet; don't report progress" "--submodule-prefix" # not for command line use "--recurse-submodules-default::" # not for command line use "t --tags; fetch all tags from the remote" "v --verbose" # TODO description ) #<# command -f completion/git::fetch:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--recurse-submodules|--upload-pack) command -f completion/git::$ARGOPT:arg ;; (--recurse-submodules-default) ;; # (--depth) # ;; ('') typeset i=2 multiple=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) i=$((i+1)) break ;; (--multiple) multiple=true break ;; (-?*) i=$((i+1)) ;; (*) break ;; esac done if $multiple || [ $i -gt ${WORDS[#]} ]; then #TODO complete remote URI command -f completion/git::completeremote { command -vf completion/git::completeremotegroup >/dev/null 2>&1 || . -AL completion/git-remote; } && command -f completion/git::completeremotegroup elif [ "${WORDS[-1]}" = tag ]; then command -f completion/git::completeref --tags else typeset word="${TARGETWORD#"$PREFIX"}" word=${word#+} case $word in (*:*) # complete local refs word=${word#*:} PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeref ;; (*) # complete remote refs PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeremoteref "${WORDS[i]}" ;; esac fi ;; esac } function completion/git::fetch:getopt { typeset fetch= case ${gitcmd-} in fetch) fetch=true;; esac OPTIONS=("$OPTIONS" #># "--all; fetch all remotes" "a --append; append to (not overwrite) existing FETCH_HEAD" "--depth:; specify the max number of history to fetch" "f --force; allow non-fast-forward update" "k --keep; keep downloaded pack" "--no-recurse-submodules; don't fetch submodules" "${fetch+n} --no-tags; don't fetch tags automatically" "--progress; report progress" "--recurse-submodules::; specify whether to fetch submodules" "u --update-head-ok" # not for command line use "--upload-pack:; specify a path for git-upload-pack on the remote host" ) #<# } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-format-patch000066400000000000000000000045121354143602500207650ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-format-patch" command. # Supports Git 1.7.7. function completion/git-format-patch { WORDS=(git format-patch "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::format-patch:arg { OPTIONS=( #># "--add-header:; specify an additional header string" "--attach::; make patch contents an mail attachment" "--cc:; specify an additional receiver" "--cover-letter; create a file containing overall diffstat" "--ignore-if-in-upstream" "--inline::; like --attach, but use \"Content-Disposition: inline\"" "--in-reply-to:" "k --keep-subject; don't add/remove \"[PATCH]\"" "--no-attach; cancel the --attach option" "--no-binary; don't include diffs for binary files" "p --no-stat; create plain patches without diffstats" "N --no-numbered; name output in \"[PATCH]\" format" "--no-signature; don't append a signature to results" "--no-thread; don't thread mails" "n --numbered; name output in \"[PATCH n/m]\" format" "--numbered-files; use simple integers for output filenames" "o: --output-directory:; specify the directory to place the results in" "--quiet; don't print created patch filenames" "--root; treat the operand as a revision range" "--signature:; specify a signature appended to each message" "s --signoff; include a signed-off-by line" "--start-number:; specify a number to start numbering patches from" "--stdout; output results to the standard output rather than files" "--subject-prefix:; specify a string prefix to the subject" "--suffix:; specify a suffix appended to result filenames" "--thread::; specify mail threading behavior" "--to:; specify an additional receiver" ) #<# if command -vf completion/git::diff:getopt >/dev/null 2>&1 || . -AL completion/git-diff; then command -f completion/git::diff:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--add-header) ;; (--attach|--inline) ;; (--in-reply-to) ;; (o|--output-directory) complete -P "$PREFIX" -S / -T -d ;; (--signature) ;; (--start-number) ;; (--subject-prefix) ;; (--suffix) ;; (--to|--cc) ;; ('') command -f completion/git::completeref range=true ;; (*) command -f completion/git::diff:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-grep000066400000000000000000000074071354143602500173430ustar00rootroot00000000000000# (C) 2013-2018 magicant # Completion script for the "git-grep" command. # Supports Git 2.19.1. function completion/git-grep { WORDS=(git grep "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::grep:arg { OPTIONS=( #># "A: --after-context:; print specified number of lines after each line printed" "--all-match; require all patterns to match in each file" "--and; join two patterns" "a --text; treat binary files as test files" "B: --before-context:; print specified number of lines before each line printed" "--break; insert an empty line between matches from different files" "--cached; search files in the index" "--color::; specify when to print colored output" "--column; print column numbers" "C: --context:; print specified number of lines before and after each line printed" "c --count; print only the count of selected lines" "E --extended-regexp; use extended regular expression" "e:; specify a pattern to match" "--exclude-standard; don't search ignored files" "F --fixed-strings; perform simple string matching rather than regular expression" "f: --file:; specify a file containing patterns to match" "--full-name; print filenames relative to the working tree's root directory" "G --basic-regexp; use basic regular expression" "H; always print the filename for each line printed" "h; never print filenames in results" "--heading; print filenames in separate lines" "I; assume binary files don't match anything" "i --ignore-case; case-insensitive matching" "L --files-without-match; print only the names of files containing no selected lines" "l --files-with-matches --name-only; print filenames only" "--max-depth:; specify directory depth to limit search" "n --line-number; print line numbers" "--no-color; like --color=never" "--no-exclude-standard; search ignored files" "--no-index; search files outside a working tree" "--not; negate a pattern" "--no-textconv; ignore textconv settings" "O:: --open-files-in-pager::; open matching files in a pager" "o --only-matching; print only the matching part of line" "--or; join two patterns" "p --show-function; print the name of the function containing the match" "P --perl-regexp; use Perl's regular expression" "q --quiet; don't print anything to the standard output" "--recurse-submodules; search submodules as well" "--textconv; honor textconv settings" "--threads:; specify the number of worker threads" "--untracked; search untracked files as well" "v --invert-match; select non-matching lines" "W --function-context; print the whole functions containing matches" "w --word-regexp; force the pattern to match whole words only" "z --null; print a null byte after each filename" ) #<# command -f completion/git::grep:removeparen command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ([ABC]|--*context|--max*|--threads) ;; (--color) command -f completion/git::--color:arg ;; (O|--open-files-in-pager) complete -P "$PREFIX" --external-command ;; ('') if command -f completion/git::grep:shouldcompleteuntracked; then complete -P "$PREFIX" -f fi command -f completion/git::completerefpath ;; (*) complete -P "$PREFIX" -f ;; esac } function completion/git::grep:removeparen { typeset i=2 while [ "$i" -le "${WORDS[#]}" ]; do case ${WORDS[i]} in ([\(\)]) # remove this parenthesis WORDS=("${WORDS[1,i-1]}" "${WORDS[i+1,-1]}") ;; (--) break ;; (*) : $((i++)) ;; esac done : words = "$WORDS" } function completion/git::grep:shouldcompleteuntracked { typeset i=2 while [ "$i" -le "${WORDS[#]}" ]; do case ${WORDS[i]} in (--no-index|--untracked) return 0;; (--) return 1;; esac : $((i++)) done return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-init000066400000000000000000000023031354143602500173370ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-init" command. # Supports Git 1.7.7. function completion/git-init { WORDS=(git init "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::init:arg { OPTIONS=( #># "--bare; create a bare repository" "q --quiet; print error and warning messages only" "--separate-git-dir:; specify the repository directory" "--shared::; share the repository with other users" "--template:; specify a directory that contains templates" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -P "$PREFIX" -S / -T -d ;; (*) command -f completion/git::init:compopt ;; esac } function completion/git::init:compopt case $ARGOPT in (--shared) #>># complete -P "$PREFIX" -D "set permissions according to the current umask" umask false complete -P "$PREFIX" -D "make the repository group-writable" group true complete -P "$PREFIX" -D "make the repository world-writable" all world everybody ;; #<<# (--separate-git-dir|--template) complete -P "$PREFIX" -S / -T -d ;; (*) return 1 ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-log000066400000000000000000000040121354143602500171540ustar00rootroot00000000000000# (C) 2011-2016 magicant # Completion script for the "git-log" command. # Supports Git 2.11.0. function completion/git-log { WORDS=(git log "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::log:arg { OPTIONS=() command -f completion/git::log:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::log:compopt ;; esac } function completion/git::log:getopt { OPTIONS=("$OPTIONS" #># "--bisect; show commits between good and bad only" "--decorate::; show ref names of commits" "--follow; show history beyond filename renaming" "--full-diff; show diffs for all files affected in each commit" "L:; specify a range in a file to trace history" "--log-size; print its size before printing log messages" "--no-decorate; like --decorate=no" "--source; show from which ref each commit was reached" "--use-mailmap; canonicalize authors, committers and emails" ) #<# if command -vf completion/git::rev-list:getopt >/dev/null 2>&1 || . -AL completion/git-rev-list; then command -f completion/git::rev-list:getopt fi if command -vf completion/git::diff-tree:getopt >/dev/null 2>&1 || . -AL completion/git-diff-tree; then command -f completion/git::diff-tree:getopt fi } function completion/git::log:compopt case $ARGOPT in (--decorate) #>># complete -P "$PREFIX" -D "like short, only if outputting to a terminal" auto complete -P "$PREFIX" -D "show full ref names" full complete -P "$PREFIX" -D "show ref names without prefixes" short complete -P "$PREFIX" -D "don't show ref names" no ;; #<<# (L) case $TARGETWORD in (?*:*) typeset w="${TARGETWORD#?*:}" typeset PREFIX="${TARGETWORD%"$w"}" command -f completion/git::completepath -r esac ;; (*) command -f completion/git::rev-list:compopt || command -f completion/git::diff:compopt ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-ls-remote000066400000000000000000000020201354143602500202770ustar00rootroot00000000000000# (C) 2013 magicant # Completion script for the "git-ls-remote" command. # Supports Git 1.8.1.4. function completion/git-ls-remote { WORDS=(git ls-remote "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::ls-remote:arg { OPTIONS=( #># "--exit-code; return a non-zero exit status if no refs were printed" "--get-url; just print remote URL" "--heads; print branches only" "--tags; print tags only" "u: --upload-pack:; specify a path for git-upload-pack on the remote host" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (u|--upload-pack) command -f completion/git::--upload-pack:arg ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then complete -P "$PREFIX" -- \ $( (ls -- "$(git rev-parse --git-dir)/branches") 2>/dev/null) command -f completion/git::completeremote else command -f completion/git::completeref fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-merge000066400000000000000000000065441354143602500175060ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-merge" command. # Supports Git 1.7.7. function completion/git-merge { WORDS=(git merge "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::merge:arg { OPTIONS=( #># "--abort; reset to the state before starting merge" "m:; specify the message" "--no-rerere-autoupdate; disable the rerere mechanism" "--rerere-autoupdate; enable the rerere mechanism" ) #<# command -f completion/git::merge:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; (*) command -f completion/git::merge:compopt ;; esac } function completion/git::merge:getopt { OPTIONS=("$OPTIONS" #># "--commit; commit the merge result automatically" "--ff; fast-forward if possible" "--ff-only; allow fast-forward only" "--log::; specify the number of commits in the merged branch of which the messages are reused" "--no-commit; don't commit the merge result automatically" "--no-ff; don't fast-forward even if possible" "--no-log; don't reuse messages from the commits on the merged branch" "--no-progress; don't print progress info" "--no-squash; cancel the --squash option" "n --no-stat --no-summary; don't print a diffstat" "--progress; print progress info" "q --quiet; don't print anything" "--squash; like --no-commit, but don't set MERGE_HEAD" "--stat --summary; print a diffstat" "s: --strategy:; specify the merge strategy" "X: --strategy-option:; specify a strategy-specific option" "v --verbose" # TODO description ) #<# } function completion/git::merge:compopt case $ARGOPT in (m|--log) ;; (s|--strategy) #>># complete -P "$PREFIX" -D "standard algorithm for merging more than 2 heads" octopus complete -P "$PREFIX" -D "simple algorithm that makes no change" ours complete -P "$PREFIX" -D "recursive 3-way merge" recursive complete -P "$PREFIX" -D "safe and fast 3-way merge" resolve complete -P "$PREFIX" -D "recursive 3-way merge with subtree matching" subtree ;; #<<# (X|--strategy-option) typeset i=2 strategy=recursive while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (-s*) strategy=${WORDS[i]#-s} ;; (--strategy=*) strategy=${WORDS[i]#--strategy=} ;; (--) break ;; esac i=$((i+1)) done case $strategy in (recursive) #>># complete -P "$PREFIX" -D "resolve conflicts by discarding remote changes" ours complete -P "$PREFIX" -D "resolve conflicts by discarding local changes" theirs complete -P "$PREFIX" -D "use the patience diff algorithm" patience complete -P "$PREFIX" -D "discard conflicting changes of the number of spaces" ignore-space-change complete -P "$PREFIX" -D "discard conflicting changes of spaces" ignore-all-space complete -P "$PREFIX" -D "discard conflicting changes of spaces at end of line" ignore-space-at-eol complete -P "$PREFIX" -D "run hooks to obtain trees to merge" renormalize complete -P "$PREFIX" -D "don't run hooks to obtain trees to merge" no-renormalize complete -P "$PREFIX" -D "specify the threshold for detecting renames" -T rename-threshold= complete -P "$PREFIX" -D "match directory trees before merging" subtree ;; #<<# esac ;; (*) return 1 ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-name-rev000066400000000000000000000016511354143602500201130ustar00rootroot00000000000000# (C) 2012 magicant # Completion script for the "git" command. # Supports Git 1.8.0.2. function completion/git-name-rev { WORDS=(git name-rev "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::name-rev:arg { OPTIONS=( #># "--all; print all commits reachable from any refs" "--always; show uniquely abbreviated commit object as fallback" "--name-only; don't print SHA-1 before each name" "--no-undefined; exit with non-zero status for an undefined reference" "--refs:; specify refs that should be used by a pattern" "--stdin; filter the standard input, appending a name to each SHA-1" "--tags; use tag names only" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--refs) command -f completion/git::completeref ;; ('') command -f completion/git::completeref ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-notes000066400000000000000000000074401354143602500175330ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "git-notes" command. # Supports Git 2.6.2. function completion/git-notes { WORDS=(git notes "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::notes:arg { OPTIONS=( #># "--ref:; specify a note ref to operate on" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--ref) command -f completion/git::completeref \ abbrprefixes='refs/ refs/notes/' ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -lt 1 ]; then complete -P "$PREFIX" add append copy edit \ get-ref list show merge prune remove else if command -vf "completion/git::notes:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::notes:${WORDS[1]}:arg" fi fi ;; esac } function completion/git::notes:add:arg { OPTIONS=( #># "C: --reuse-message:; specify a blob used as the note message" "c: --reedit-message:; like -C, but reedit the message" "F: --file:; specify a file containing the note message" "f --force; overwrite existing note" "m: --message:; specify a note message" "--allow-empty; allow an empty note" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([Cc]|--reuse-message|--reedit-message|'') command -f completion/git::completeref ;; (F|--file) complete -P "$PREFIX" -f ;; esac } function completion/git::notes:append:arg { command -f completion/git::notes:add:arg "$@" } function completion/git::notes:copy:arg { OPTIONS=( #># "f --force; overwrite existing note" "--stdin; read object names to remove notes from from standard input" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; esac } function completion/git::notes:edit:arg { OPTIONS=( #># "--allow-empty; allow an empty note" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; esac } function completion/git::notes:list:arg { command -f completion/git::completeref } function completion/git::notes:merge:arg { OPTIONS=( #># "--abort; abort the ongoing merge" "--commit; finalize the ongoing merge" "q --quiet; don't print informational messages" "s: --strategy:; specify a strategy to resolve conflict" "v --verbose; print detailed messages" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (s|--strategy) #>># complete -P "$PREFIX" -D "catenate and sort note lines uniquely" cat_sort_uniq complete -P "$PREFIX" -D "edit work tree" manual complete -P "$PREFIX" -D "discard remote changes" ours complete -P "$PREFIX" -D "discard local changes" theirs complete -P "$PREFIX" -D "catenate notes" union ;; #<<# ('') command -f completion/git::completeref \ abbrprefixes='refs/ refs/notes/' ;; esac } function completion/git::notes:prune:arg { OPTIONS=( #># "n --dry-run; don't actually remove notes" "v --verbose; print detailed messages" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; esac } function completion/git::notes:remove:arg { OPTIONS=( #># "--ignore-missing; ignore operands without notes" "--stdin; read object names to remove notes from from standard input" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; esac } function completion/git::notes:show:arg { command -f completion/git::completeref } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-pull000066400000000000000000000032041354143602500173510ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-pull" command. # Supports Git 1.7.7. function completion/git-pull { WORDS=(git pull "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::pull:arg { OPTIONS=( #># "--no-rebase; cancel the --rebase option" "q --quiet; don't report progress" "--rebase; rebase the current branch instead of merging" "v --verbose" # TODO description ) #<# if command -vf completion/git::fetch:getopt >/dev/null 2>&1 || . -AL completion/git-fetch; then command -f completion/git::fetch:getopt fi if command -vf completion/git::merge:getopt >/dev/null 2>&1 || . -AL completion/git-merge; then command -f completion/git::merge:getopt fi command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--recurse-submodules|--upload-pack) command -f completion/git::$ARGOPT:arg ;; # (--depth) # ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then #TODO complete remote URI command -f completion/git::completeremote elif [ "${WORDS[-1]}" = tag ]; then command -f completion/git::completeref --tags else typeset word="${TARGETWORD#"$PREFIX"}" word=${word#+} case $word in (*:*) # complete local refs word=${word#*:} PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeref ;; (*) # complete remote refs PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeremoteref "${WORDS[1]}" ;; esac fi ;; (*) command -f completion/git::merge:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-push000066400000000000000000000050201354143602500173520ustar00rootroot00000000000000# (C) 2011-2014 magicant # Completion script for the "git-push" command. # Supports Git 2.0.1. function completion/git-push { WORDS=(git push "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::push:arg { OPTIONS=( #># "--all; push all local branches" "--delete; delete remote refs specified as operands" "n --dry-run; don't actually push anything" "--follow-tags; push reachable annotated tags as well" "f --force; allow non-fast-forward update" "--force-with-lease::; like --force, but prevent unexpected history loss" "--mirror; push all local refs" "--no-force-with-lease; cancel the --force-with-lease option" "--no-thin; cancel the --thin option" "--no-verify; disable the pre-push hook" "--porcelain; print in the machine-friendly format" "--progress; report progress" "--prune; delete remote branches that have no local counterparts" "q --quiet; don't report progress" "--repo:; specify the default repository to push to" "--receive-pack: --exec:; specify a path for git-receive-pack on the remote host" "--recurse-submodules:; ensure submodule commits are available on the remote" "u --set-upstream; make pushed branches remote-tracking" "--tags; push all local tags" "--thin; send a thin pack to reduce traffic" "v --verbose" # TODO description "--verify; enable the pre-push hook" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--force-with-lease) typeset word="${TARGETWORD#"$PREFIX"}" word=${word#*:} PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeref ;; (--receive-pack|--exec) command -f completion/git::--receive-pack:arg ;; (--recurse-submodules) #>># complete -P "$PREFIX" -D "check if submodules have been pushed" check complete -P "$PREFIX" -D "push submodules as necessary" on-demand ;; #<<# (--repo|'') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then #TODO complete remote URI command -f completion/git::completeremote elif [ "${WORDS[-1]}" = tag ]; then command -f completion/git::completeref --tags else typeset word="${TARGETWORD#"$PREFIX"}" word=${word#+} case $word in (*:*) # complete remote refs word=${word#*:} PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeremoteref "${WORDS[1]}" ;; (*) # complete local refs PREFIX=${TARGETWORD%"$word"} command -f completion/git::completeref ;; esac fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-rebase000066400000000000000000000047071354143602500176470ustar00rootroot00000000000000# (C) 2011-2013 magicant # Completion script for the "git-rebase" command. # Supports Git 1.8.1.4. function completion/git-rebase { WORDS=(git rebase "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::rebase:arg { OPTIONS=() command -f completion/git::rebase:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; (*) command -f completion/git::rebase:compopt ;; esac } function completion/git::rebase:getopt { OPTIONS=("$OPTIONS" #># "--abort; abort the current rebasing and reset to the original" "--autosquash; find commits to be squashed (with -i)" "--committer-date-is-author-date; use author date for committer date" "--continue; continue the current rebasing" "--edit-todo; re-edit the to-do list of the current rebasing" "f --force-rebase; rebase even if the branch is up-to-date" "--ignore-date; use committer date for author date" "i --interactive; interactively reedit commits that are rebased" "--keep-empty; don't omit commits that make no change" "m --merge; use merging strategies to rebase" "--no-autosquash; cancel the --autosquash option" "--no-ff; don't fast-forward even if possible" "n --no-stat; don't print a diffstat" "--no-verify; don't run the pre-rebase hook" "--onto; specify a branch to rebase onto" "p --preserve-merges; don't ignore merge commits" "q --quiet; don't print anything" "--root; rebase all ancestor commits" "--skip; skip the current patch and continue rebasing" "--stat; print a diffstat from the last rebase" "s: --strategy:; specify the merge strategy" "x: --exec:; insert an \"exec\" line with the specified command after each commit (with -i)" "X: --strategy-option:; specify a strategy-specific option" "v --verbose" # TODO description "--verify; run the pre-rebase hook" ) #<# if command -vf completion/git::apply:getopt >/dev/null 2>&1 || . -AL completion/git-apply; then command -f completion/git::apply:getopt rebase fi } function completion/git::rebase:compopt case $ARGOPT in (*) if command -vf completion/git::apply:compopt >/dev/null 2>&1 || . -AL completion/git-apply; then command -f completion/git::apply:compopt fi if command -vf completion/git::merge:compopt >/dev/null 2>&1 || . -AL completion/git-merge; then command -f completion/git::merge:compopt fi ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-reflog000066400000000000000000000020741354143602500176570ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "git-reflog" command. # Supports Git 2.6.2. function completion/git-reflog { WORDS=(git reflog "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::reflog:arg if [ ${WORDS[#]} -le 1 ]; then complete -P "$PREFIX" delete exists expire show else WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::reflog:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::reflog:${WORDS[1]}:arg" fi fi function completion/git::reflog:delete:arg { # Options are not supported since the delete subcommand is not mainly # for interactive use. command -f completion/git::completeref } function completion/git::reflog:exists:arg { command -f completion/git::completeref } # Not supported, since the expire subcommand is not mainly for interactive use. # function completion/git::reflog:expire:arg function completion/git::reflog:show:arg { WORDS=(git log -g --abbrev-commit --pretty=oneline "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-remote000066400000000000000000000112461354143602500176750ustar00rootroot00000000000000# (C) 2011-2019 magicant # Completion script for the "git-remote" command. # Supports Git 1.7.7. function completion/git-remote { WORDS=(git remote "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::remote:arg { while [ x"${WORDS[2]}" = x"-v" ] || [ x"${WORDS[2]}" = x"--verbose" ]; do WORDS=("${WORDS[1]}" "${WORDS[3,-1]}") done if [ ${WORDS[#]} -le 1 ]; then case ${TARGETWORD#"$PREFIX"} in (-v) # avoid completing "-v" to "-vv" complete -P "$PREFIX" -- -v ;; (-*) OPTIONS=( #># "v --verbose; list remotes with URLs" ) #<# command -f completion//parseoptions command -f completion//completeoptions ;; (*) #>># complete -P "$PREFIX" -D "add a remote" add complete -P "$PREFIX" -D "rename a remote" rename complete -P "$PREFIX" -D "remove a remote" rm complete -P "$PREFIX" -D "set the default branch of a remote" set-head complete -P "$PREFIX" -D "set remote-tracking branches" set-branches complete -P "$PREFIX" -D "set the URL of a remote" set-url complete -P "$PREFIX" -D "show a remote" show complete -P "$PREFIX" -D "delete remote-tracking branches that no longer exist on a remote" prune complete -P "$PREFIX" -D "fetch remotes" update ;; #<<# esac else WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::remote:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::remote:${WORDS[1]}:arg" fi fi } function completion/git::remote:add:arg { OPTIONS=( #># "f; fetch the remote after adding" "m:; specify a remote branch to be refs/remotes/*/HEAD" "--mirror:; specify a mirroring method" "--no-tags; don't fetch all remote tags after adding" "t:; specify a branch to track" "--tags; fetch all remote tags after adding" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([mt]) command -f completion/git::completeref --branches ;; (--mirror) #>># complete -P "$PREFIX" -D "mirror the remote into the local" fetch complete -P "$PREFIX" -D "set the remote.*.mirror option" push ;; #<<# ('') command -f completion/git::completeremote ;; esac } function completion/git::remote:rename:arg { command -f completion/git::completeremote } function completion/git::remote:rm:arg { command -f completion/git::completeremote } function completion/git::remote:set-head:arg { case ${WORDS[1]} in (set-head) OPTIONS=( #># "a --auto; query the remote default branch" "d --delete; remove the remote default branch" ) #<# ;; (set-branches) OPTIONS=( #># "a --add; add branch; don't remove existing settings" ) #<# ;; esac command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then command -f completion/git::completeremote else command -f completion/git::completeremoteref \ "${WORDS[1]}" fi ;; esac } function completion/git::remote:set-branches:arg { command -f completion/git::remote:set-head:arg "$@" } function completion/git::remote:set-url:arg { OPTIONS=( #># "--add; add a URL instead of changing URLs" "--delete; remove URLs instead of changing URLs" "--push; set push URLs instead of fetch URLs" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeremote ;; esac } function completion/git::remote:show:arg { OPTIONS=( #># "n; don't query the remote for the latest info" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeremote ;; esac } function completion/git::remote:prune:arg { OPTIONS=( #># "n --dry-run; don't actually prune, but show what would happen" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeremote ;; esac } function completion/git::remote:update:arg { OPTIONS=( #># "p --prune; delete remote-tracking branches that no longer exist on the remote" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeremote command -f completion/git::completeremotegroup ;; esac } function completion/git::completeremotegroup { typeset name value while read -r name value; do complete -P "$PREFIX" -D "= $value" -- "${name#remotes.}" done 2>/dev/null <(git config --get-regexp 'remotes\..*') } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-request-pull000066400000000000000000000015401354143602500210400ustar00rootroot00000000000000# (C) 2012 magicant # Completion script for the "git" command. # Supports Git 1.8.0.2. function completion/git-request-pull { WORDS=(git request-pull "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::request-pull:arg { OPTIONS=( #># "p; print a patch" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands case ${WORDS[#]} in (1) command -f completion/git::request-pull:compurl ;; (*) command -f completion/git::completeref ;; esac ;; esac } function completion/git::request-pull:compurl { typeset name url type while read -r name url type; do complete -P "$PREFIX" -D "$name $type" -- "$url" done 2>/dev/null <(git remote -v) } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-reset000066400000000000000000000015011354143602500175150ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-reset" command. # Supports Git 1.7.7. function completion/git-reset { WORDS=(git reset "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::reset:arg { OPTIONS=( #># "--hard; reset the index and working tree" "--keep; like --hard, but keep working tree changes" "--merge; reset out of a conflicted merge" "--mixed; reset the index but keep the working tree" "p --patch; interactively choose patch hunks to reset" "q --quiet; print error and warning messages only" "--soft; keep the index and working tree intact" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-rev-list000066400000000000000000000116671354143602500201560ustar00rootroot00000000000000# (C) 2011-2016 magicant # Completion script for the "git-rev-list" command. # Supports Git 2.9.2. function completion/git-rev-list { WORDS=(git rev-list "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::rev-list:arg { OPTIONS=( #># "--bisect; print a midpoint commit in current bisect" "--bisect-all" "--bisect-vars" "--count; print the number of selected commits only" "--header; print commits in the raw format" "--objects; print object IDs referenced by selected commits" "--objects-edge; like --objects, but print excluded commits too" "--objects-edge-aggressive; like --objects-edge, but more slow and accurate" "--indexed-objects; print object IDs referenced by the index" "--timestamp; print the raw timestamp values" "--unpacked; print object IDs that are not in packs" ) #<# command -f completion/git::rev-list:getopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::rev-list:compopt ;; esac } function completion/git::rev-list:getopt { command -f completion/git::getorderopts command -f completion/git::getprettyopts command -f completion/git::getrefselectopts OPTIONS=("$OPTIONS" #># "--ancestry-path; show commits on the ancestor-descendant path only" "--all-match; show commits that match all the other filter options only" "--after: --since:; show commits after the specified date only" "--author:; show commits by the specified author only" "--basic-regexp; use basic regular expression" "--before: --until:; show commits before the specified date only" "--boundary; show excluded boundary commits" "--cherry; like --right-only --cherry-mark --no-merges" "--cherry-mark; like --cherry-pick, but mark commits" "--cherry-pick; omit commits duplicated by cherry-picking" "--children; print children's commit IDs as well" "--committer:; show commits by the specified committer only" "--date:; specify a date format" "--dense; show commits that have a diff" "--do-walk; traverse commit ancestors" "E --extended-regexp; use extended regular expression" "--first-parent; follow first parent of each commit only" "F --fixed-strings; perform simple string matching rather than regular expression" "--full-history; follow all parents of merges even if the parents have no diff" "--graph; print commit ancestry tree graph" "--grep:; show commits whose log message matches the specified pattern only" "--grep-reflog:; show commits whose reflog message matches the specified pattern only" "--ignore-missing; ignore nonexistent refs" "--invert-grep; show commits that don't match --grep=..." "--left-only; show commits on the left-hand-side branch only" "--left-right; show reachability of commits from branches" # not meant for interactive use: "--max-age:" "n: --max-count:; specify the max number of commits shown" "--max-parents:; show commits with at most the specified number of parents only" "--merge; show refs that touch conflicting files" "--merges; like --min-parents=2 (show merge commits only)" # not meant for interactive use: "--min-age:" "--min-parents:; show commits with at least the specified number of parents only" "--no-max-parents; like --max-parents=-1" "--no-merges; like --max-parents=1 (don't show merge commits)" "--no-min-parents; like --min-parents=0" "--no-walk::; don't traverse commit ancestors" "--parents; print parents' commit IDs as well" "--perl-regexp; use Perl's regular expression" "--quiet; print nothing" "i --regexp-ignore-case; case-insensitive regular expression matching" "--reflog; show all reflogs" "--relative-date; like --date=relative" "--remove-empty; stop when a given path disappears from the tree" "--reverse; print in reverse order" "--right-only; show commits on the right-hand-side branch only" "--show-linear-break::; show a separator between branches" "--simplify-by-decoration; show branch/tag heads only" "--simplify-merges; don't show merges that re-merge an ancestor" "--sparse; show all walked commits" "--stdin; read arguments from the standard input" "--use-bitmap-index" "g --walk-reflogs; show reflogs instead of ancestry chain" ) #<# # "--not" is not included in this list because it is actually # an operand rather than an option. } function completion/git::rev-list:compopt case $ARGOPT in (n|--max-*|--min-*) # complete nothing ;; (--author|--date) command -f "completion/git::$ARGOPT:arg" ;; (--after|--before|--since|--until) # TODO complete date ;; (--committer) typeset committer while read -r committer; do complete -P "$PREFIX" -- "$committer" done 2>/dev/null \ <(git log --all --format=format:%cn | uniq) ;; (--no-walk) complete -P "$PREFIX" sorted unsorted ;; (*) command -f completion/git::completeprettyopts || command -f completion/git::completerefselectopts ;; esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-rev-parse000066400000000000000000000047701354143602500203120ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "git-rev-parse" command. # Supports Git 2.9.2. function completion/git-rev-parse { WORDS=(git rev-parse "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::rev-parse:arg { OPTIONS=( #># "--abbrev-ref::; print in short object names" "--after: --since:; convert the specified date to --max-age timestamp" "--before: --until:; convert the specified date to --mim-age timestamp" "--default; specify a default argument" "--disambiguate:; show all SHA-1 values that start with the specified prefix" "--flags; print options only" "--git-common-dir; print the main repository path" "--git-dir; print the repository path" "--git-path; resolve a path inside the repository" "--is-bare-repository; test if the repository is bare" "--is-inside-git-dir; test if the current directory is inside a repository" "--is-inside-work-tree; test if the current directory is inside a working tree" "--keep-dashdash" "--local-env-vars; print repository-local environment variables" "--no-flags; print operands only" "--no-revs; ignore arguments meant for rev-list" "--not; negate printed object names" "--parseopt; enable option parsing mode" "--prefix; specify a directory to operate in" "q --quiet; don't print any error message (with --verify)" "--resolve-git-dir; test if the operand is a repository path" "--revs-only; ignore arguments not meant for rev-list" "--shared-index-path; print the shared index file path" "--short::; abbreviate printed SHA-1 values" "--show-cdup; print a relative path to the top-level directory" "--show-prefix; print a relative path from the top-level directory" "--show-toplevel; print the absolute path of the top-level directory" "--sq; print with shell-friendly quotation" "--sq-quote; enable shell quoting mode" "--stop-at-non-option" "--stuck-long" "--symbolic; keep operands symbolic" "--symbolic-full-name; convert refs into full names" "--verify; verify if the operand names a valid object" ) #<# command -f completion/git::getrefselectopts command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--disambiguate) ;; (--abbrev-ref) #>># complete -P "$PREFIX" -D "allow ambiguous names" loose complete -P "$PREFIX" -D "ensure unambiguous names" strict ;; #<<# ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::completerefselectopts ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-revert000066400000000000000000000023071354143602500177070ustar00rootroot00000000000000# (C) 2011-2016 magicant # Completion script for the "git-revert" command. # Supports Git 2.9.3. function completion/git-revert { WORDS=(git revert "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::revert:arg { OPTIONS=( #># "--abort; end suspended revert and restore the original state" "--continue; resume suspended revert" "e --edit; (re)edit the message" "m: --mainline:; specify the mainline parent by number" "n --no-commit; don't commit the reversion result automatically" "--no-edit; don't reedit the message" "--quit; end suspended quit and keep the current state" "S:: --gpg-sign::; sign commits with GPG" "s --signoff; add a \"signed-off-by\" line to the message" "--strategy:; specify the merge strategy" "X: --strategy-option:; specify a strategy-specific option" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref range=true ;; (*) if command -vf completion/git::merge:compopt >/dev/null 2>&1 || . -AL completion/git-merge; then command -f completion/git::merge:compopt fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-rm000066400000000000000000000014211354143602500170120ustar00rootroot00000000000000# (C) 2015 magicant # Completion script for the "git-rm" command. # Supports Git 2.2.2. function completion/git-rm { WORDS=(git rm "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::rm:arg { OPTIONS=( #># "--cached; remove from index and leave files untracked" "n --dry-run; don't actually remove files" "f --force; forcibly remove files with uncommitted changes" "--ignore-unmatch; pretend success even if no files matched" "q --quiet; don't report which files are being removed" "-r; remove directories recursively" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completepath -r ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-show000066400000000000000000000013571354143602500173640ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-show" command. # Supports Git 1.7.7. function completion/git-show { WORDS=(git show "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::show:arg { OPTIONS=() { command -vf completion/git::log:getopt >/dev/null 2>&1 || . -AL completion/git-log; } && command -f completion/git::log:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeobject ;; (*) { command -vf completion/git::log:compopt >/dev/null 2>&1 || . -AL completion/git-log; } && command -f completion/git::log:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-show-branch000066400000000000000000000025471354143602500206210ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-show-branch" command. # Supports Git 1.7.7. function completion/git-show-branch { WORDS=(git show-branch "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::show-branch:arg { OPTIONS=( #># "a --all; show local and remotes-tracking branches" "--color::; show symbols in color" "--current; show the current branch" "--independent; show branch heads not contained by any others" "--list; like --more=-1 (show branch heads only)" "--merge-base; show possible merge bases" "--more:; specify the number of ancestors shown beyond the common ancestor" "--no-color; like --color=never" "--no-name; don't show commit names" "g:: --reflog::; show ref-log instead of branches" "r --remotes; show remote-tracking branches" "--sha1-name; show SHA1 prefixes instead of symbolic commit names" "--sparse; don't omit merges that aren't common ancestors of branches shown" "--topics; show branches not contained in the first one only" ) #<# command -f completion/git::getorderopts command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--color) command -f completion/git::--color:arg ;; # (--more) # ;; # (g|--reflog) # ;; ('') command -f completion/git::completeref ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-stash000066400000000000000000000104231354143602500175200ustar00rootroot00000000000000# (C) 2011-2018 magicant # Completion script for the "git-stash" command. # Supports Git 2.18.0. function completion/git-stash { WORDS=(git stash "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::stash:arg if [ ${WORDS[#]} -le 1 ]; then #>># complete -P "$PREFIX" -D "restore stash contents" apply complete -P "$PREFIX" -D "restore stash contents in a new branch" branch complete -P "$PREFIX" -D "remove all stashes" clear complete -P "$PREFIX" -D "create a stash commit without adding it to the stash list" create complete -P "$PREFIX" -D "remove stash contents without restoring it" drop complete -P "$PREFIX" -D "list existing stashes" list complete -P "$PREFIX" -D "restore stash contents and remove it from the stash list" pop complete -P "$PREFIX" -D "save local changes in a new stash and reset to HEAD" push complete -P "$PREFIX" save complete -P "$PREFIX" -D "show stash contents" show complete -P "$PREFIX" -D "add an existing commit to the stash list" store #<<# command -f completion/git::stash:push:arg else WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::stash:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::stash:${WORDS[1]}:arg" else case ${WORDS[1]} in (-*) command -f completion/git::stash:push:arg esac fi fi function completion/git::stash:apply:arg { command -f completion/git::stash:pop:arg } function completion/git::stash:branch:arg { if [ ${WORDS[#]} -le 1 ]; then command -f completion/git::completeref --branches else command -f completion/git::stash:completestash fi } #function completion/git::stash:clear:arg { #} #function completion/git::stash:create:arg { #} function completion/git::stash:drop:arg { command -f completion/git::stash:pop:arg } function completion/git::stash:list:arg { OPTIONS=() if command -vf completion/git::log:getopt >/dev/null 2>&1 || . -AL completion/git-log; then command -f completion/git::log:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') ;; (*) command -f completion/git::log:compopt ;; esac } function completion/git::stash:pop:arg { OPTIONS=( #># "q --quiet; print error messages only" ) #<# case ${WORDS[1]} in (apply|pop) OPTIONS=("$OPTIONS" #># "--index; restore the index as well as the working copy" ) #<# esac command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::stash:completestash ;; esac } function completion/git::stash:push:arg { OPTIONS=( #># "a --all; stash all files including ignored/untracked files" "k --keep-index; keep the index intact" "p --patch; interactively choose patch hunks to stash" "q --quiet; print error messages only" "--no-keep-index; stash and reset the index" "u --include-untracked; stash untracked files as well as tracked files" ) #<# case ${WORDS[1]} in (push|-*) OPTIONS=("$OPTIONS" #># "m: --message:; specify the stash message" ) #<# esac command -f completion//parseoptions -n case $ARGOPT in (-) if [ "${WORDS[#]}" -le 1 ]; then WORDS=(push "$WORDS") fi command -f completion//completeoptions ;; ('') case ${WORDS[1]} in (-*) typeset i for i in "${WORDS}"; do if [ "$i" = -- ]; then WORDS=(push "$WORDS") break fi done esac case ${WORDS[1]} in (push) command -f completion/git::completefilteredpath '' \ --ignore-submodules=dirty esac ;; esac } function completion/git::stash:save:arg { command -f completion/git::stash:push:arg } function completion/git::stash:show:arg { OPTIONS=() if command -vf completion/git::diff:getopt >/dev/null 2>&1 || . -AL completion/git-diff; then command -f completion/git::diff:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::stash:completestash ;; (*) command -f completion/git::diff:compopt ;; esac } function completion/git::stash:completestash { typeset name message while read -r name message; do name=${name%:} complete -P "$PREFIX" -D "$message" -- "$name" done 2>/dev/null <(git stash list) } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-status000066400000000000000000000021701354143602500177210ustar00rootroot00000000000000# (C) 2011-2014 magicant # Completion script for the "git-status" command. # Supports Git 1.8.5.3. function completion/git-status { WORDS=(git status "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::status:arg { OPTIONS=( #># "b --branch; print the current branch" "--column::; columnize output" "--ignored; print ignored files as well" "--ignore-submodules::; ignore changes to submodules" "--long --no-short; print in the long format" "--no-column; print files line by line" "--porcelain; print in the machine-friendly format" "s --short; print in the short format" "u:: --untracked-files::; print untracked files" "z; print a null byte after each filename" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--column) command -f completion/git::--column:arg ;; (--ignore-submodules) command -f completion/git::--ignore-submodules:arg ;; (u|--untracked-files) command -f completion/git::--untracked-files:arg ;; ('') complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-submodule000066400000000000000000000117261354143602500204040ustar00rootroot00000000000000# (C) 2013-2016 magicant # Completion script for the "git-submodule" command. # Supports Git 1.8.1.4. function completion/git-submodule { WORDS=(git submodule "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::submodule:arg { OPTIONS=( #># "q --quiet; print error messages only" ) #<# while [ x"${WORDS[2]}" = x"-q" ] || [ x"${WORDS[2]}" = x"--quiet" ]; do WORDS=("${WORDS[1]}" "${WORDS[3,-1]}") done if [ ${WORDS[#]} -le 1 ]; then case ${TARGETWORD#"$PREFIX"} in (-q) # avoid completing "-q" to "-qq" complete -P "$PREFIX" -- -q ;; (-*) command -f completion//parseoptions command -f completion//completeoptions ;; (*) #>># complete -P "$PREFIX" -D "add a submodule" add complete -P "$PREFIX" -D "unregister submodules" deinit complete -P "$PREFIX" -D "execute a shell command in each submodule" foreach complete -P "$PREFIX" -D "configure submodules according to .gitmodules" init complete -P "$PREFIX" -D "show status of submodules" status complete -P "$PREFIX" -D "print summary of changes in submodules" summary complete -P "$PREFIX" -D "reset submodule remote URL" sync complete -P "$PREFIX" -D "clone and check out submodules" update ;; #<<# esac else WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::submodule:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::submodule:${WORDS[1]}:arg" fi fi } function completion/git::submodule:add:arg { OPTIONS=("$OPTIONS" #># "b: --branch:; specify a branch of the submodule to clone" "--depth:; specify the max number of history to clone" "f --force; add an ignored path" "--name:; specify the name of the submodule" "--reference:; specify a reference repository to share objects (possibly dangerous operation)" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions -e ;; # (b|--branch) # ;; # (--name) # ;; (--reference) complete -P "$PREFIX" -S / -T -d ;; ('') command -f completion//getoperands if command -vf completion/git::clone:opr >/dev/null 2>&1 || . -AL completion/git-clone; then command -f completion/git::clone:opr fi ;; esac } function completion/git::submodule:deinit:arg { OPTIONS=("$OPTIONS" #># "f --force; overwrite local changes or ignore unmerged files" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -P "$PREFIX" -S / -T -d ;; esac } function completion/git::submodule:foreach:arg { OPTIONS=("$OPTIONS" #># "--recursive; operate on all submodules recursively" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands command -f completion//reexecute -e ;; esac } function completion/git::submodule:init:arg { complete -P "$PREFIX" -S / -T -d } function completion/git::submodule:status:arg { OPTIONS=("$OPTIONS" #># "--cached; print status of the supermodule index" "--recursive; operate on all submodules recursively" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -P "$PREFIX" -S / -T -d ;; esac } function completion/git::submodule:summary:arg { OPTIONS=("$OPTIONS" #># "--cached; compare the supermodule HEAD and index" "--files; compare the supermodule index with the submodule HEAD" "n: --summary-limit:; specify the max number of commits to show" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (n|--summary-limit) # ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then command -f completion/git::completeref fi complete -P "$PREFIX" -S / -T -d ;; esac } function completion/git::submodule:sync:arg { OPTIONS=("$OPTIONS" #># "--recursive; operate on all submodules recursively" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') complete -P "$PREFIX" -S / -T -d ;; esac } function completion/git::submodule:update:arg { OPTIONS=("$OPTIONS" #># "--depth:; specify the max number of history to clone" "f --force; overwrite local changes or ignore unmerged files" "--init; do \"git submodule init\" before updating" "--merge; merge in submodules" "N --no-fetch; don't fetch from submodule remotes" "--rebase; rebase in submodules" "--recursive; operate on all submodules recursively" "--reference:; specify a reference repository to share objects (possibly dangerous operation)" "--remote; update submodules to their remote's latest commit" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--reference) complete -P "$PREFIX" -S / -T -d ;; ('') complete -P "$PREFIX" -S / -T -d ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-svn000066400000000000000000000333031354143602500172060ustar00rootroot00000000000000# (C) 2011-2015 magicant # Completion script for the "git-svn" command. # Supports Git 1.7.7. function completion/git-svn { WORDS=(git svn "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::svn:arg if [ ${WORDS[#]} -le 1 ]; then #>># complete -P "$PREFIX" -D "commit a diff to a Subversion repository" commit-diff complete -P "$PREFIX" -D "commit local commits to the Subversion repository" dcommit complete -P "$PREFIX" -D "commit to the Subversion repository without checking for conflicts" set-tree complete -P "$PREFIX" -D "convert commit IDs and revision numbers" find-rev complete -P "$PREFIX" -D "create a branch" branch complete -P "$PREFIX" -D "create a gitignore file" create-ignore complete -P "$PREFIX" -D "create a tag" tag complete -P "$PREFIX" -D "fetch and rebase local commits" rebase complete -P "$PREFIX" -D "fetch revisions from a remote Subversion repository" fetch complete -P "$PREFIX" -D "init and fetch" clone complete -P "$PREFIX" -D "initialize a Git repository associated with a Subversion repository" init complete -P "$PREFIX" -D "print a property of a file" propget complete -P "$PREFIX" -D "print candidates for the info/exclude file" show-ignore complete -P "$PREFIX" -D "print external repositories" show-externals complete -P "$PREFIX" -D "print info about files" info complete -P "$PREFIX" -D "print properties of a file" proplist complete -P "$PREFIX" -D "print revision log" log complete -P "$PREFIX" -D "recreate empty directories" mkdirs complete -P "$PREFIX" -D "reduce local repository size" gc complete -P "$PREFIX" -D "show a file with commit info" blame complete -P "$PREFIX" -D "undo fetch" reset else #<<# WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::svn:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::svn:${WORDS[1]}:arg" fi fi function completion/git::svn:blame:arg { OPTIONS=( #># "--git-format; print in the git blame format" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completepath -a ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:branch:arg { OPTIONS=( #># "--commit-url:; specify a repository URL to commit to" "d: --destination:; specify a branch/tag path to operate on" "n --dry-run; don't actually make a branch/tag" "m: --message:; specify the commit message" "t --tag; create a tag rather than a branch" "--username:; specify a user name for authentication" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--destination) # TODO ;; # (m|--message) # ;; ('') command -f completion/git::completeref --branches ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:clone:arg { OPTIONS=( #># "--preserve-empty-dirs; create empty directories with dummy files" "--placeholder-filename:; specify the name of dummy files in empty directories" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:fetch:getopt command -f completion/git::svn:init:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--placeholder-filename) complete -P "$PREFIX" -f ;; # ('') # # Hmm... How can we complete a URL? # ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:commit-diff:arg { OPTIONS=() command -f completion/git::svn:getcommonopt command -f completion/git::svn:commit-diff:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (l) # #TODO # ;; ('') command -f completion//getoperands case ${WORDS[#]} in ([01]) command -f completion/git::completeref ;; # (2) # # Hmm... How can we complete a URL? # ;; esac ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:commit-diff:getopt { OPTIONS=("$OPTIONS" #># "--add-author-from; add an author name to each log message" "e --edit; reedit the message" "--find-copies-harder" #TODO "l:" #TODO "--rmdir; remove empty directories from the Subversion tree" ) #<# command -f completion/git::svn:getauthoropt } function completion/git::svn:create-ignore:arg { OPTIONS=( #># "r: --revision:; specify a revision to show" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (r|--revision) # ;; # ('') # ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:dcommit:arg { OPTIONS=( #># "--commit-url:; specify a repository URL to commit to" "n --dry-run; don't actually commit anything" "m --merge; use merging strategies to rebase" "--mergeinfo:; specify mergeinfo to add" "--no-rebase; don't rebase after committing" "s: --strategy:; specify the merge strategy" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:commit-diff:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (l) # #TODO # ;; (s|--strategy) if command -vf completion/git::rebase:compopt >/dev/null 2>&1 || . -AL completion/git-rebase; then command -f completion/git::rebase:compopt fi ;; ('') command -f completion/git::completeref ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:fetch:arg { OPTIONS=( #># "--parent; fetch only from the Subversion parent of the current HEAD" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:fetch:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::svn:completesvnremotes ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:fetch:getopt { OPTIONS=("$OPTIONS" #># "--ignore-paths:; specify a regular expression whose matching pathnames are ignored when fetching" "--localtime; store Git commit dates in the local timezone" #"--repack::" obsolete option #"--repack-flags:" obsolete option "--use-log-author; find author names out of log messages" ) #<# command -f completion/git::svn:getauthoropt } function completion/git::svn:find-rev:arg { OPTIONS=() command -f completion/git::svn:getcommonopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completeref ;; (*) command -f completion/git::svn:compopt ;; esac } #function completion/git::svn:gc:arg { #} function completion/git::svn:info:arg { OPTIONS=( #># "--url; only print the URL of the remote Subversion repository" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completepath -a ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:init:arg { OPTIONS=("$OPTIONS" #># "--shared::; share the repository with other users" "--template:; specify a directory that contains templates" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:init:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # ('') # # Hmm... How can we complete a URL? # ;; (*) { { command -vf completion/git::init:compopt >/dev/null 2>&1 || . -AL completion/git-init; } && command -f completion/git::init:compopt; } || command -f completion/git::svn:compopt ;; esac } function completion/git::svn:init:getopt { OPTIONS=("$OPTIONS" #># "b: --branches:; specify the branches subdirectory name" "--ignore-paths:; specify a regular expression whose matching pathnames are ignored when fetching" "--no-metadata; set the noMetadata option" "--no-minimize-url; don't normalize URL to the repository root" "--prefix:; specify the prefix for trunk/branches/tags" "--rewrite-root:; specify the rewriteRoot option value" "--rewrite-uuid:; specify the rewriteUUID option value" "s --stdlayout; follow the standard trunk/branches/tags directory layout" "t: --tags:; specify the tags subdirectory name" "T: --trunk:; specify the trunk subdirectory name" "--username:; specify a user name for authentication" "--use-svm-props; set the useSvmProps option" "--use-svnsync-props; set the useSvnsyncProps option" ) #<# } function completion/git::svn:log:arg { OPTIONS=( #># "--incremental; print output in a format suitable for concatenation" "--limit:; specify the number of revisions to show at most" "--oneline; use only one line for each revision" "r: --revision:; specify a revision (range) to show" "--show-commit; show git commit IDs as well" "v --verbose; print additional info" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (--limit) # ;; # (r|--revision) # ;; ('') command -f completion/git::completepath -a ;; (*) command -f completion/git::svn:compopt ;; esac } #function completion/git::svn:mkdirs:arg { #} function completion/git::svn:propget:arg { OPTIONS=( #># "r: --revision:; specify a revision to show" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (r|--revision) # ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then if command -vf completion/svn::completepropname >/dev/null 2>&1 || . -AL completion/svn; then command -f completion/svn::completepropname fi else command -f completion/git::completepath -r fi ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:proplist:arg { OPTIONS=( #># "r: --revision:; specify a revision to show" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (r|--revision) # ;; ('') command -f completion/git::completepath -r ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:rebase:arg { OPTIONS=( #># "n --dry-run; just print the remote branch name and URL" "l --local; don't fetch remotely; just rebase to already-fetched revision" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:fetch:getopt if command -vf completion/git::rebase:getopt >/dev/null 2>&1 || . -AL completion/git-rebase; then command -f completion/git::rebase:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') ;; (*) { { command -vf completion/git::rebase:compopt >/dev/null 2>&1 || . -AL completion/git-rebase; } && command -f completion/git::rebase:compopt; } || command -f completion/git::svn:compopt ;; esac } function completion/git::svn:reset:arg { OPTIONS=( #># "p --parent; discard the specified revision as well" "r: --revision:; specify the most recent revision to keep" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (r|--revision) # ;; # ('') # ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:set-tree:arg { OPTIONS=( #># "--stdin; read a list of commits from the standard input and commit them" ) #<# command -f completion/git::svn:getcommonopt command -f completion/git::svn:commit-diff:getopt command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; # (l) # #TODO # ;; ('') command -f completion/git::completeref ;; (*) command -f completion/git::svn:compopt ;; esac } function completion/git::svn:show-externals:arg { OPTIONS=( #># "r: --revision:; specify a revision to show" ) #<# command -f completion/git::svn:getcommonopt command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; # (r|--revision) # ;; # ('') # ;; (*) command -f completion/git::svn:compopt ;; esac } #function completion/git::svn:show-ignore:arg { #} function completion/git::svn:tag:arg { command -f completion/git::svn:branch:arg } function completion/git::svn:getcommonopt { OPTIONS=("$OPTIONS" #># "q --quiet; print less messages" ) #<# } function completion/git::svn:getauthoropt { OPTIONS=("$OPTIONS" #># "A: --authors-file:; specify a file containing author names" "--authors-prog:; specify a program that returns an author name and email" ) #<# } function completion/git::svn:compopt case $ARGOPT in (A|--authors-file|--ignore-paths|--placeholder-filename) complete -P "$PREFIX" -f ;; (--authors-prog) WORDS=() command -f completion//reexecute -e ;; # (b|--branches) # #TODO # ;; # (--commit-url) # # Hmm... How can we complete a URL? # ;; # (l) # #TODO # ;; (--username) complete -P "$PREFIX" -u ;; (*) return 1 ;; esac function completion/git::svn:completesvnremotes { typeset name url while read -r name url; do complete -P "$PREFIX" -D "$url" -- "${{name#*.}%.*}" done 2>/dev/null <(git config --get-regexp 'svn-remote\..*\.url') } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-tag000066400000000000000000000026001354143602500171470ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "git-tag" command. # Supports Git 1.7.7. function completion/git-tag { WORDS=(git tag "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::tag:arg { OPTIONS=( #># "a; make an unsigned annotated tag" "--contains:; list tags that are ancestors of the specified commit" "d; delete tags" "F:; specify a file containing the message" "f --force; overwrite an existing tag" "l; list tag names that match an operand" "m:; specify the message" "n::; specify the number of lines of annotation to print" "s; make a GPG-signed tag with the default email address's key" "u:; specify a key to make a GPG-signed tag with" "v; verify tags" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; (--contains) command -f completion/git::completeref ;; ('') typeset i=2 nomake=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) i=$((i+1)) break ;; (--*) i=$((i+1)) ;; (-*[dlv]*) nomake=true break ;; (-?*) i=$((i+1)) ;; (*) break ;; esac done if $nomake || [ $i -gt ${WORDS[#]} ]; then command -f completion/git::completeref --tags else command -f completion/git::completeref fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-whatchanged000066400000000000000000000016421354143602500206560ustar00rootroot00000000000000# (C) 2013 magicant # Completion script for the "git-whatchanged" command. # Supports Git 1.8.1.4. function completion/git-whatchanged { WORDS=(git whatchanged "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::whatchanged:arg { OPTIONS=() if command -vf completion/git::diff-tree:getopt >/dev/null 2>&1 || . -AL completion/git-diff-tree; then command -f completion/git::diff-tree:getopt fi if command -vf completion/git::rev-list:getopt >/dev/null 2>&1 || . -AL completion/git-rev-list; then command -f completion/git::rev-list:getopt fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::diff-tree:compopt || command -f completion/git::rev-list:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/git-worktree000066400000000000000000000034401354143602500202410ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "git-worktree" command. # Supports Git 2.7.0. function completion/git-worktree { WORDS=(git worktree "${WORDS[2,-1]}") command -f completion//reexecute } function completion/git::worktree:arg if [ ${WORDS[#]} -le 1 ]; then complete -P "$PREFIX" add prune list else WORDS=("${WORDS[2,-1]}") if command -vf "completion/git::worktree:${WORDS[1]}:arg" >/dev/null 2>&1; then command -f "completion/git::worktree:${WORDS[1]}:arg" fi fi function completion/git::worktree:add:arg { OPTIONS=( #># "B:; create or reset a new branch and check it out" "b:; create a new branch and check it out" "--detach; leave HEAD in detached head state" "f --force; allow multiple working trees for single branch" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([Bb]) command -f completion/git::completeref --branches ;; ('') command -f completion//getoperands case ${WORDS[#]} in (0) complete -P "$PREFIX" -S / -T -d ;; (1) command -f completion/git::completeref ;; esac ;; esac } function completion/git::worktree:prune:arg { OPTIONS=( #># "--expire:; specify age of working trees to remove" "n --dry-run; don't remove any working trees" "v --verbose; report removed working trees" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--expire) # TODO complete date ;; esac } function completion/git::worktree:list:arg { OPTIONS=( #># "--porcelain; print in the machine-friendly format" ) #<# command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gitg000066400000000000000000000024511354143602500165510ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "gitg" command. # Supports gitg 0.2.5. function completion/gitg { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--bisect; show commits between good and bad only" "--class:; specify a program class used by the window manager" "c --commit; start in the commit mode" "--display:; specify the X display to use" "--g-fatal-warnings; make all GTK+ warnings fatal" "--gtk-module:; specify an additional GTK+ module to load" "h ? --help; print help" "--help-all; print help for all options" "--help-gtk; print help for GTK+ options" "--name:; specify a program name used by the window manager" "-s --select; select commit after loading the repository" "V --version; print version info" ) #<# if command -vf completion/git >/dev/null 2>&1 || . -AL completion/git; then if command -vf completion/git::rev-list:getopt >/dev/null 2>&1 || . -AL completion/git-rev-list; then command -f completion/git::rev-list:getopt fi fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') # TODO support repository operands command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::rev-list:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gitk000066400000000000000000000003471354143602500165570ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "gitk" command. # Supports Git 1.7.7. function completion/gitk { WORDS=(git rev-list "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gitx000066400000000000000000000014621354143602500165730ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "gitx" command. # Supports GitX 0.7. function completion/gitx { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--bisect; show commits between good and bad only" "c --commit; start in the commit mode" "h --help; print help" ) #<# if command -vf completion/git >/dev/null 2>&1 || . -AL completion/git; then if command -vf completion/git::rev-list:getopt >/dev/null 2>&1 || . -AL completion/git-rev-list; then command -f completion/git::rev-list:getopt fi fi command -f completion//parseoptions -n case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion/git::completerefpath range=true ;; (*) command -f completion/git::rev-list:compopt ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gnutar000066400000000000000000000002671354143602500171220ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "gnutar" command. function completion/gnutar { command -f completion//reexecute tar } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/grep000066400000000000000000000110631354143602500165530ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "grep" command. # Supports POSIX 2008, GNU grep 2.6.3, SunOS 5.10, HP-UX 11i v3. function completion/grep { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU grep'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "c ${long:+--count}; print only the count of selected lines" "E ${long:+--extended-regexp}; use extended regular expression" "e: ${long:+--regexp:}; specify a pattern to match" "F ${long:+--fixed-strings}; perform simple string matching rather than regular expression" "f: ${long:+--file:}; specify a file containing patterns to match" "i ${long:+--ignore-case}; case-insensitive matching" "l ${long:+--files-with-matches}; print filenames only" "n ${long:+--line-number}; print line numbers" "q ${long:+--quiet --silent}; don't print anything to the standard output" "s ${long:+--no-messages}; suppress error messages" "v ${long:+--invert-match}; select non-matching lines" "x ${long:+--line-regexp}; force the pattern to match whole lines only" ) #<# ADDOPTIONS=() case $type in (GNU|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "h ${long:+--no-filename}; never print filenames in results" "w ${long:+--word-regexp}; force the pattern to match whole words only" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "A: --after-context:; print specified number of lines after each line printed" "a --text; treat binary files as test files" "B: --before-context:; print specified number of lines before each line printed" "b --byte-offset; print byte offset for each line printed" "C: --context:; print specified number of lines before and after each line printed" "D: --devices:; specify how to handle special files" "d: --directories:; specify how to handle directories" "G --basic-regexp; use basic regular expression" "H --with-filename; always print the filename for each line printed" "I; assume binary files don't match anything" "L --files-without-match; print only the names of files containing no selected lines" "m: --max-count:; specify the count to match at most" "o --only-matching; print only the matching part of line" "P --perl-regexp; use Perl's regular expression" "R r --recursive; recursively search directories" "T --initial-tab; align result lines" "U --binary; don't convert CR-LF into LF" "u --unix-byte-offsets; report offsets ignoring carriage returns" "V --version; print version info" "Z --null; print a null byte after each filename" "z --null-data; separate output lines with null byte rather than newline" "--binary-files:; specify how to handle binary files" "--color:: --colour::; specify when to print colored output" "--exclude:; skip files whose names match the specified pattern" "--exclude-dir:; skip directories whose names match the specified pattern" "--exclude-from:; skip files whose names match a pattern in the specified file" "--include:; search only files whose names match the specified pattern" "--label:; specify a filename for the standard output" "--line-buffered; print results as soon as possible" "--help" ) #<# ;; (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "b; print 512-block offset for each line printed" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([ABCm]|--*context|--max-count) ;; (--binary-files) #>># complete -P "$PREFIX" -D "report but don't print the contents of binary files" binary complete -P "$PREFIX" -D "assume binary files don't match anything" without-match complete -P "$PREFIX" -D "treat binary files as test files" text ;; #<<# (--color|--colour) #>># complete -P "$PREFIX" -D "always print in color" yes always force complete -P "$PREFIX" -D "print in color if output is terminal" auto tty if-tty complete -P "$PREFIX" -D "don't print in color" no never none ;; #<<# (D|--devices) #>># complete -P "$PREFIX" -D "treat special files as regular files" read complete -P "$PREFIX" -D "skip special files" skip ;; #<<# (d|--directories) #>># complete -P "$PREFIX" -D "treat directories as regular files" read complete -P "$PREFIX" -D "recursively search directories" recurse complete -P "$PREFIX" -D "skip directories" skip ;; #<<# (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gtar000066400000000000000000000002631354143602500165530ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "gtar" command. function completion/gtar { command -f completion//reexecute tar } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gview000066400000000000000000000003041354143602500167330ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "gview" command. # Supports Vim 7.3. function completion/gview { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gvim000066400000000000000000000003021354143602500165520ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "gvim" command. # Supports Vim 7.3. function completion/gvim { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/gvimdiff000066400000000000000000000003121354143602500174040ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "gvimdiff" command. # Supports Vim 7.3. function completion/gvimdiff { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/hash000066400000000000000000000013501354143602500165370ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "hash" built-in command. function completion/hash { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a --all; don't exclude built-ins when printing cached paths" "d --directory; manipulate caches for home directory paths" "r --remove; remove cached paths" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset directory=false word for word in "${WORDS[2,-1]}"; do case $word in (-d|--directory) directory=true ;; (--) break ;; esac done if $directory; then complete -u else complete --external-command fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/head000066400000000000000000000030321354143602500165140ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "head" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/head { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "n: ${long:+--lines:}; specify the number of lines to print" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "c: ${long:+--bytes:}; specify the number of bytes to print" ) #<# case $type in (GNU|NetBSD) OPTIONS=("$OPTIONS" #># "q ${long:+--quiet --silent}; never print filename headers" "v ${long:+--verbose}; always print filename headers" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# esac esac ;; (HP-UX) OPTIONS=("$OPTIONS" #># "c; count text in bytes rather than lines" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([cn]|--bytes|--lines) case $type in (GNU) if command -vf completion//prefixdigits >/dev/null 2>&1 || . -AL completion/_blocksize; then if command -f completion//prefixdigits; then command -f completion//completesizesuffix b GNU fi fi esac ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/help000066400000000000000000000005361354143602500165510ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "help" built-in command. function completion/help { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -b ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/history000066400000000000000000000015141354143602500173170ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "history" built-in command. function completion/history { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c --clear; clear the history completely" "d: --delete:; clear the specified history item" "F --flush-file; refresh the history file" "r: --read:; read history from the specified file" "s: --set:; replace the last history item with the specified command" "w: --write:; write history to the specified file" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (d|--delete|s|--set) typeset num cmd while read -r num cmd; do complete -P "$PREFIX" -D "$num" -- "$cmd" done <(fc -l 1) ;; (r|--read|w|--write) complete -P "$PREFIX" -f ;; (*) ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/iconv000066400000000000000000000040721354143602500167360ustar00rootroot00000000000000# (C) 2010-2013 magicant # Completion script for the "iconv" command. # Supports POSIX 2008, GNU libc 2.12.1, GNU libiconv 1.13, NetBSD 5.0, # SunOS 5.10, HP-UX 11i v3. function completion/iconv { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU libc'*) typeset type=glibc ;; (*'GNU libiconv'*) typeset type=glibiconv ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (glibc|glibiconv) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c; discard unconvertible characters" "f: ${long:+--from-code:}; specify the input encoding" "l ${long:+--list}; print all supported encodings" "s ${long:+--silent}; suppress warnings about unconvertible characters" "t: ${long:+--to-code:}; specify the output encoding" ) #<# case $type in (glibc) OPTIONS=("$OPTIONS" #># "o: --output:; specify the file to output" "? --help; print help" "--verbose; print progress info" "V --version; print version info" ) #<# ;; (glibiconv) OPTIONS=("$OPTIONS" #># "--unicode-subst:; specify a format to substitute unconvertible Unicode characters with" "--byte-subst:; specify a format to substitute unconvertible bytes with" "--widechar-subst:; specify a format to substitute invalid wide characters with" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([ft]|--from-code|--to-code) command -f completion/iconv::compenc "${WORDS[1]}" ;; (*) complete -P "$PREFIX" -f ;; esac } function completion/iconv::compenc { typeset iconv="${1-iconv}" case $("$iconv" --version 2>/dev/null) in (*'GNU libc'*) typeset type=glibc ;; (*'GNU libiconv'*) typeset type=glibiconv ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac # POSIX does not specify the format of `iconv -l' output. # We support GNU libc and libiconv for now. case $type in (glibc|glibiconv) complete -P "$PREFIX" -- $("$iconv" -l 2>/dev/null) esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/id000066400000000000000000000030121354143602500162050ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "id" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/id { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "G ${long:+--groups}; print all group IDs" "g ${long:+--group}; print the group ID only" "n ${long:+--name}; print the user/group name rather than number" "r ${long:+--real}; print the real ID rather than the effective ID" "u ${long:+--user}; print the user ID only" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "Z --context; print the security context only" "--help" "--version" ) #<# ;; (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "p; print in a human-readable form" ) #<# case $type in (FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "A; print process audit properties" "M; print the MAC label" "P; print in the passwd file format" ) #<# esac ;; (SunOS) OPTIONS=("$OPTIONS" #># "p; print project membership" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "P; print the process resource group ID" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -u ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/jobs000066400000000000000000000016241354143602500165550ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "jobs" built-in command. # Completion function "completion/jobs" is used for the "fg", "bg", "disown" # built-ins as well. function completion/jobs { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# case ${WORDS[1]} in (disown) OPTIONS=("$OPTIONS" #># "a --all; disown all jobs" ) #<# ;; (jobs) OPTIONS=("$OPTIONS" #># "l --verbose; print process IDs of processes" "n --new; print only jobs whose status change is not yet reported" "p --pgid-only; print process group IDs only" "r --running-only; print running jobs only" "s --stopped-only; print stopped jobs only" ) #<# ;; esac command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) case $TARGETWORD in (%*) PREFIX=% esac complete -P "$PREFIX" -j ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/join000066400000000000000000000025111354143602500165530ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "join" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/join { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "1:; join on the nth field of the first file" "2:; join on the nth field of the second file" "a:; print unpairable lines from the nth file" "e:; specify a string to replace missing fields with" "o:; specify the output format" "t:; specify the field separator character" "v:; only print unpairable lines from the nth file" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "i --ignore-case; case-insensitive comparison" "--check-order; accept correctly sorted input only" "--nocheck-order; don't check that the input is correctly sorted" "--help" "--version" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([12jot]) ;; ([av]) complete -P "$PREFIX" 1 2 ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/kill000066400000000000000000000027401354143602500165530ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "kill" built-in command. function completion/kill { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "s: n:; specify a signal to send" "l; print signal names" "v; print signal names and description" "--help" ) #<# typeset signame=false SAVEWORDS SAVEWORDS=("$WORDS") command -f completion//parseoptions case $ARGOPT in (-) case $TARGETWORD in (-[[:upper:]]*) signame=true PREFIX=- ;; (-[[:digit:]]*) typeset signame=true word for word in "${WORDS[2,-1]}" "${SAVEWORDS[2,-1]}"; do case $word in (-[ns[:upper:][:digit:]]*) signame=false esac done if $signame; then PREFIX=- fi ;; (*) command -f completion//completeoptions return ;; esac ;; ([ns]) signame=true ;; (*) typeset word for word in "${WORDS[2,-1]}"; do case $word in (-[lv]) signame=true esac done ;; esac if $signame; then complete -P "$PREFIX" --signal else typeset pid args case $TARGETWORD in (%*) # complete a job name complete -P % -j ;; (-*) # complete a process group ID while read -r pid args; do if kill -n 0 -$pid; then complete -D "$args" -- "-$pid" fi done 2>/dev/null <(ps -A -o pgid= -o args=) ;; (*) # complete a process ID while read -r pid args; do if kill -n 0 $pid; then complete -D "$args" -- "$pid" fi done 2>/dev/null <(ps -A -o pid= -o args=) ;; esac fi } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ksh000066400000000000000000000002541354143602500164030ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ksh" command. function completion/ksh { command -f completion//reexecute set } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/less000066400000000000000000000225241354143602500165700ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "less" command. # Supports less 436. function completion/less { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "? --help; show help" "a --search-skip-screen; don't match in the current screen when searching" "B --auto-buffers; don't increase the buffer size for data from the standard input" "b: --buffers:; specify the buffer size in kilobytes" "c C --clear-screen --CLEAR-SCREEN; don't scroll the screen" "d --dumb; don't print an error message for a dumb terminal" "E --QUIT-AT-EOF; exit immediately after printing the last line of the last file" "e --quit-at-eof; exit when scrolling beyond the last line of the last file" "F --quit-if-one-screen; exit immediately if the entire file fits one screen" "f --force; open non-regular files" "g --hilite-search; highlight matches for the last search only" "G --HILITE-SEARCH; don't highlight any matches for searches" "--old-bot; use the old bottom-of-screen behavior" "h: --max-back-scroll:; specify the max number of lines to scroll backward" "I --IGNORE-CASE; case-insensitive search" "i --ignore-case; case-insensitive search if the pattern is in all lowercase" "J --status-column; show a status column at the left" "j: --jump-target:; specify the line where a jump target is positioned" "K --quit-on-intr; exit when interrupted" "k: --lesskey-file:; specify a lesskey file" "L --no-lessopen; ignore the \$LESSOPEN variable" "M --LONG-PROMPT; use the very long prompt" "m --long-prompt; use the long prompt" "N --LINE-NUMBERS; show line numbers at the left" "n --line-numbers; don't handle line numbers" "O: --LOG-FILE:; copy the standard input to the specified file (always overwrite)" "o: --log-file:; copy the standard input to the specified file" "P: --prompt:; specify the prompt" "p: --pattern:; start at the specified pattern" "Q --QUIET --SILENT; never use the terminal bell" "q --quiet --silent; don't use the terminal bell for minor errors" "R --RAW-CONTROL-CHARS; output terminal control sequence as is" "r --raw-control-chars; output all control characters as is" "S --chop-long-lines; don't wrap long lines" "s --squeeze-blank-lines; squeeze adjacent empty lines into one" "T: --tag-file:; specify the tags file" "t: --tag:; specify an identifier to jump" "U --UNDERLINE-SPECIAL; disable special treatment of backspace, tab and carriage return" "u --underline-special; disable special treatment of backspace and carriage return" "V --version; print version info" "w --HILITE-UNREAD; highlight the new line after any forward movement" "w --hilite-unread; highlight the new line after forward-screen" "X --no-init; don't initialize the screen" "x: --tabs:; specify the width of a tab" "--no-keypad; don't initialize the screen for the keypad" "y: --max-forw-scroll:; specify the forward scroll limit" "z: --window:; specify the vertical scroll amount" "\": --quotes:; specify the shell quote characters" "~ --tilde; don't show tildes after the end of file" "#: --shift:; specify the horizontal scroll amount" "--follow-name; reopen the file when the file is recreated during the F command" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([bhjxyz\"#]|--buffers|--max-back-scroll|--jump-target|--tabs\ |--max-forw-scroll|--window|--quotes|--shift) ;; (P|--prompt) typeset word="${TARGETWORD#"$PREFIX"}" if [ -z "$word" ]; then #>># complete -T -P "$PREFIX" -D "set the result format of the = command" = complete -T -P "$PREFIX" -D "set the help screen prompt" h complete -T -P "$PREFIX" -D "set the very long prompt" M complete -T -P "$PREFIX" -D "set the long prompt" m complete -T -P "$PREFIX" -D "set the short prompt" s complete -T -P "$PREFIX" -D "set the F command prompt" w else #<<# case $word in (*%*) typeset prefix="${TARGETWORD%\%*}" #>># complete -T -P "$prefix" -D "byte offset of the top line" '%bt' complete -T -P "$prefix" -D "byte offset of the middle line" '%bm' complete -T -P "$prefix" -D "byte offset of the bottom line" '%bb' complete -T -P "$prefix" -D "byte offset of the line just after the bottom" '%bB' complete -T -P "$prefix" -D "byte offset of the target line" '%bj' complete -T -P "$prefix" -D "file size" '%B' '%s' complete -T -P "$prefix" -D "column number of the leftmost column" '%c' complete -T -P "$prefix" -D "page number of the top line" '%dt' complete -T -P "$prefix" -D "page number of the middle line" '%dm' complete -T -P "$prefix" -D "page number of the bottom line" '%db' complete -T -P "$prefix" -D "page number of the line just after the bottom" '%dB' complete -T -P "$prefix" -D "page number of the target line" '%dj' complete -T -P "$prefix" -D "number of pages" '%D' complete -T -P "$prefix" -D "editor name" '%E' complete -T -P "$prefix" -D "filename" '%f' complete -T -P "$prefix" -D "index of the current file in the operands" '%i' complete -T -P "$prefix" -D "line number of the top line" '%lt' complete -T -P "$prefix" -D "line number of the middle line" '%lm' complete -T -P "$prefix" -D "line number of the bottom line" '%lb' complete -T -P "$prefix" -D "line number of the line just after the bottom" '%lB' complete -T -P "$prefix" -D "line number of the target line" '%lj' complete -T -P "$prefix" -D "number of lines" '%L' complete -T -P "$prefix" -D "number of the operands files" '%m' complete -T -P "$prefix" -D "line number percent of the top line" '%Pt' complete -T -P "$prefix" -D "line number percent of the middle line" '%Pm' complete -T -P "$prefix" -D "line number percent of the bottom line" '%Pb' complete -T -P "$prefix" -D "line number percent of the line just after the bottom" '%PB' complete -T -P "$prefix" -D "line number percent of the target line" '%Pj' complete -T -P "$prefix" -D "byte offset percent of the top line" '%pt' complete -T -P "$prefix" -D "byte offset percent of the middle line" '%pm' complete -T -P "$prefix" -D "byte offset percent of the bottom line" '%pb' complete -T -P "$prefix" -D "byte offset percent of the line just after the bottom" '%pB' complete -T -P "$prefix" -D "byte offset percent of the target line" '%pj' complete -T -P "$prefix" -D "remove trailing spaces" '%t' complete -T -P "$prefix" -D "next filename" '%x' esac #<<# case $word in (*\?*) typeset prefix="${TARGETWORD%\?*}" #>># complete -T -P "$prefix" -D "true if there is any character before here" '?a' complete -T -P "$prefix" -D "true if the value of %bt is known" '?bt' complete -T -P "$prefix" -D "true if the value of %bm is known" '?bm' complete -T -P "$prefix" -D "true if the value of %bb is known" '?bb' complete -T -P "$prefix" -D "true if the value of %bB is known" '?bB' complete -T -P "$prefix" -D "true if the value of %bj is known" '?bj' complete -T -P "$prefix" -D "true if the file size is known" '?B' '?s' complete -T -P "$prefix" -D "true if the text is horizontally shifted" '?c' complete -T -P "$prefix" -D "true if the value of %dt is known" '?dt' complete -T -P "$prefix" -D "true if the value of %dm is known" '?dm' complete -T -P "$prefix" -D "true if the value of %db is known" '?db' complete -T -P "$prefix" -D "true if the value of %dB is known" '?dB' complete -T -P "$prefix" -D "true if the value of %dj is known" '?dj' complete -T -P "$prefix" -D "true if at the end of the file" '?e' complete -T -P "$prefix" -D "true if the input file is not the standard input" '?f' complete -T -P "$prefix" -D "true if the value of %lt is known" '?lt' complete -T -P "$prefix" -D "true if the value of %lm is known" '?lm' complete -T -P "$prefix" -D "true if the value of %lb is known" '?lb' complete -T -P "$prefix" -D "true if the value of %lB is known" '?lB' complete -T -P "$prefix" -D "true if the value of %lj is known" '?lj' complete -T -P "$prefix" -D "true if the line number of the last line is known" '?L' complete -T -P "$prefix" -D "true if there are more than one input file" '?m' complete -T -P "$prefix" -D "true if is the first prompt" '?n' complete -T -P "$prefix" -D "true if the value of %Pt is known" '?Pt' complete -T -P "$prefix" -D "true if the value of %Pm is known" '?Pm' complete -T -P "$prefix" -D "true if the value of %Pb is known" '?Pb' complete -T -P "$prefix" -D "true if the value of %PB is known" '?PB' complete -T -P "$prefix" -D "true if the value of %Pj is known" '?Pj' complete -T -P "$prefix" -D "true if the value of %pt is known" '?pt' complete -T -P "$prefix" -D "true if the value of %pm is known" '?pm' complete -T -P "$prefix" -D "true if the value of %pb is known" '?pb' complete -T -P "$prefix" -D "true if the value of %pB is known" '?pB' complete -T -P "$prefix" -D "true if the value of %pj is known" '?pj' complete -T -P "$prefix" -D "true if there is a next file" '?x' esac #<<# fi ;; (t|--tag) typeset tagfile= word for word in "${WORDS[2,-1]}"; do case $word in (--tag-file=*) tagfile=${word#--tag-file=} ;; (-T*) tagfile=${word#-T} ;; (-*) ;; (*) break ;; esac done if [ -r "${tagfile:-tags}" ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 "${tagfile:-tags}" 2>/dev/null) fi ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ln000066400000000000000000000047001354143602500162270ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ln" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/ln { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "f ${long:+--force}; remove existing target" "L ${long:+--logical}; make a link to the file referred to by the symbolic link" "P ${long:+--physical}; make a link to the symbolic link itself" "s ${long:+--symbolic}; make a symbolic link rather than a hard link" ) #<# ADDOPTIONS=() case $type in (GNU|FreeBSD|NetBSD|Darwin|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "i ${long:+--interactive}; ask before overwriting existing files" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "b; like --backup=existing" "--backup::; specify how to make a backup" "d F --directory; make a hard link to a directory" "n --no-dereference; don't follow symbolic links" "S: --suffix:; specify a suffix to append to backup file names" "T --no-target-directory; always treat the destination as a regular file" "t: --target-directory:; specify a destination directory" "--help" "--version" ) #<# ;; (*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "h n; don't follow symbolic links" ) #<# case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "F; replace the target directory with a link (with -s)" ) #<# case $type in (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "w; warn when making a symbolic link to a unexisting file" ) #<# esac esac ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--backup) if command -vf completion//completebackup >/dev/null 2>&1 || . -AL completion/_backup; then command -f completion//completebackup fi ;; (S|--suffix) ;; (t|--target-directory) complete -T -P "$PREFIX" -S / -d ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/local000066400000000000000000000002751354143602500167130ustar00rootroot00000000000000# (C) 2018 magicant # Completion script for the "local" built-in command. function completion/local { command -f completion//reexecute typeset } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/locale000066400000000000000000000024621354143602500170600ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "locale" command. # Supports POSIX 2008, GNU libc 2.12.1 function completion/locale { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU libc'*) typeset type=glibc ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (glibc) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a ${long:+--all-locales}; print all available locale names" "c ${long:+--category-name}; print names of the selected categories" "k ${long:+--keyword-name}; print names of the selected keywords" "m ${long:+--charmaps}; print all available charmaps" ) #<# case $type in (glibc) OPTIONS=("$OPTIONS" #># "v --verbose; print more information" "? --help; print help" "--usage" "V --version; print version info" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) IFS=" " typeset category keywords for category in $(locale 2>/dev/null); do category=${category%%=*} case $category in (LANG|LC_ALL) ;; (*) keywords=($(locale -k "$category" 2>/dev/null)) complete -- "$category" "${keywords%%=*}" ;; esac done complete charmap ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ls000066400000000000000000000211221354143602500162310ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ls" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/ls { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "1; print files line by line" "A ${long:+--almost-all}; print all files but . and .." "a ${long:+--all}; print all files" "C; arrange filenames vertically" "c; print or sort by last status change time" "d ${long:+--directory}; print directory itself, not its contents" "F ${long:+--classify}; append symbols indicating file type" "f; print all files without sorting" "g; like -l but don't print owners" "H ${long:+--dereference-command-line}; follow symbolic links in operands" "i ${long:+--inode}; print file serial number (inode)" "k; print file size in kilobytes" "L ${long:+--dereference}; follow all symbolic links" "l; print in long format" "m; print in stream output format" "n ${long:+--numeric-uid-gid}; like -l but print owners and groups in numbers" "o; like -l but don't print group" "p; append slash to directory names" "q ${long:+--hide-control-chars}; print unprintable characters as ?" "R ${long:+--recursive}; print subdirectories recursively" "r ${long:+--reverse}; sort in reverse order" "S; sort by file size" "s ${long:+--size}; print block sizes occupied by files" "t; sort by last modified time" "u; print or sort by last access time" "x; arrange filenames horizontally" ) #<# ADDOPTIONS=() case $type in (GNU|NetBSD|FreeBSD|Darwin|SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "b ${long:+--escape}; escape special characters like C string" ) #<# case $type in (NetBSD|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "B; escape special characters as backslashed octal value" ) #<# esac esac case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "h ${long:+--human-readable}; print size using K, M, etc. for 1024^n" ) #<# esac case $type in (*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "T; print time in full (with -l)" ) #<# case $type in (NetBSD|FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "W; show whiteouts when scanning directories" "w; don't escape unprintable characters" ) #<# case $type in (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "G; print filenames in color" "P; don't follow symbolic links" "U; print or sort by file creation time" ) #<# esac esac esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "--author; print file authors (with -l)" "--block-size:; specify block size" "B --ignore-backups; don't print filenames ending with ~" "--color::; print filenames in color" "D --dired; print in emacs dired format" "--file-type; like -F but don't append * to executables" "--format:; specify output format" "--full-time; like -l --time-style=full-iso" "--group-directories-first; print directories before other files" "G --no-group; don't print group (with -l)" "--si; print size using k, M, etc. for 1000^n" "--dereference-command-line-symlink-to-dir; follow symbolic links to directory in operands" "--hide:; specify pattern to exclude from listing" "--indicator-style:; append symbols indicating file type" "I: --ignore:; specify filename pattern to exclude from listing" "N --literal; don't quote filenames" "Q --quote-name; print filenames in double-quotes" "--quoting-style:; specify how to quote filenames" "--show-control-chars; print non-graphic characters as is" "--sort:; specify sort key" "--time:; specify which time to print and sort by" "--time-style:; specify format to print time in" "T: --tabsize:; specify how many spaces a tab stands for" "U; don't sort" "v; sort by filename regarding as version number" "w: --width:; specify screen width" "X; sort by filename extension" "--lcontext; print in long format with security context" "Z --context; print in simplified long format with security context" "--scontext; print security context of files" "--help" "--version" ) #<# ;; (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "D:; specify format to print time in" "I; don't assume -A for superuser" "Z; print MAC labels" ) #<# ;; (Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "e; print access control list" "O; print file flags (with -l)" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "E; like -l, with time in ISO 8601 format" "e; like -l, with time like Dec 31 13:34:56 1999" "V; like -l, with compact access control list info" "v; like -l, with verbose access control list info" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "e; print extent attributes (with -l)" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (D) if command -vf completion//completestrftime >/dev/null 2>&1 || . -AL completion/date; then command -f completion//completestrftime fi ;; (--block-size) if command -vf completion//completeblocksize >/dev/null 2>&1 || . -AL completion/_blocksize; then command -f completion//completeblocksize fi ;; (--color) #>># complete -P "$PREFIX" -D "always print in color" yes always force complete -P "$PREFIX" -D "print in color if output is terminal" auto tty if-tty complete -P "$PREFIX" -D "don't print in color" no never none ;; #<<# (--format) #>># complete -P "$PREFIX" -D "print files line by line" single-column complete -P "$PREFIX" -D "arrange filenames vertically" vertical complete -P "$PREFIX" -D "print permission, owner, file size, etc." long verbose complete -P "$PREFIX" -D "separate filenames by commas" commas complete -P "$PREFIX" -D "arrange filenames horizontally" horizontal across complete -P "$PREFIX" -D "print security context of files" context ;; #<<# #(--hide) -- complete as filename as usual # ;; (--indicator-style) #>># complete -P "$PREFIX" -D "don't append symbols indicating file type" none complete -P "$PREFIX" -D "append slash to directory names" slash complete -P "$PREFIX" -D "enable all indicators but *" file-type complete -P "$PREFIX" -D "enable all indicators" classify ;; #<<# #(I|--ignore) -- complete as filename as usual # ;; (--quoting-style) #>># complete -P "$PREFIX" -D "quote or escape nothing" literal complete -P "$PREFIX" -D "quote in format suitable for shell input" shell complete -P "$PREFIX" -D "quote all filenames in format suitable for shell input" shell-always complete -P "$PREFIX" -D "quote and escape like C string" c complete -P "$PREFIX" -D "quote and escape like C string if necessary" c-maybe complete -P "$PREFIX" -D "escape like C string" escape complete -P "$PREFIX" -D "locale-dependent quoting and escaping" locale complete -P "$PREFIX" -D "locale-dependent quoting and escaping" clocale ;; #<<# (--sort) #>># complete -P "$PREFIX" -D "don't sort" none complete -P "$PREFIX" -D "sort by filename extension" extension complete -P "$PREFIX" -D "sort by file size" size complete -P "$PREFIX" -D "sort by last modified time" time complete -P "$PREFIX" -D "sort by filename regarding as version number" version ;; #<<# (--time) #>># complete -P "$PREFIX" -D "print or sort by last access time" atime access use complete -P "$PREFIX" -D "print or sort by last status change time" ctime status ;; #<<# (--time-style) case $TARGETWORD in ("$PREFIX+"*) if command -vf completion//completestrftime >/dev/null 2>&1 || . -AL completion/date; then command -f completion//completestrftime fi ;; (*) #>># complete -P "$PREFIX" -D "e.g. 2000-01-01 01:23:45.678901234 +0900" full-iso complete -P "$PREFIX" -D "e.g. 2010-06-29 00:37" long-iso complete -P "$PREFIX" -D "e.g. 01-23 01:23 or 2000-01-23" iso complete -P "$PREFIX" -D "locale-dependent time style" locale posix-locale complete -P "$PREFIX" -D "like full-iso, but only when not in POSIX locale" posix-full-iso complete -P "$PREFIX" -D "like long-iso, but only when not in POSIX locale" posix-long-iso complete -P "$PREFIX" -D "like iso, but only when not in POSIX locale" posix-iso complete -T -P "$PREFIX" -D "specify time format after +" + ;; #<<# esac ;; (--tabsize) ;; (w|--width) complete -P "$PREFIX" 80 132 ${COLUMNS:+"$COLUMNS"} ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/make000066400000000000000000000141051354143602500165330ustar00rootroot00000000000000# (C) 2013-2014 magicant # Completion script for the "make" command. # Supports POSIX 2008, GNU Make 3.82, FreeBSD 9.0, OpenBSD 5.0, NetBSD 3.0, # Solaris 11.1, HP-UX 11i v3. function completion/make { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU Make'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "e ${long:+--environment-overrides}; environment variables override variables defined in makefile" "f: ${long:+--file: --makefile:}; specify makefile" "i ${long:+--ignore-errors}; ignore errors returned by invoked commands" "k ${long:+--keep-going}; continue to make other targets if some can't be made" "n ${long:+--just-print --dry-run --recon}; don't actually make but print what would be done" "p ${long:+--print-data-base}; print defined macros and targets" "q ${long:+--question}; check if the target is up-to-date without making anything" "r ${long:+--no-builtin-rules}; disable the built-in rules" "S ${long:+--no-keep-going --stop}; stop immediately when some target can't be made" "s ${long:+--silent --quiet}; don't print invoked commands" "t ${long:+--touch}; touch targets instead of usual making" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD) ADDOPTIONS=("$ADDOPTIONS" #># "I: ${long:+--include-dir:}; specify a directory containing makefiles" "j: ${long:+--jobs:}; specify the number of concurrent jobs" ) #<# case $type in (GNU|FreeBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "C: ${long:+--directory:}; change to the specified directory" ) #<# case $type in (OpenBSD|NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "D:; define the specified variable to be 1" ) #<# esac esac esac case $type in (GNU|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "w ${long:+--print-directory}; print working directory name" ) #<# esac case $type in (GNU|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "d; print debugging info" ) #<# esac case $type in (SunOS|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "u; remake everything unconditionally" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "B --always-make; consider all targets out-of-date" "--debug::; specify debugging options" "--eval:; evaluate the specified rule" "h --help; print help" "L --check-symlink-times; consider the last modified time of symbolic links" "l::; --load-average:: --max-load::; keep concurrency below the specified load average" "--no-print-directory; cancel the -w option" "o: --old-file: --assume-old:; don't remake targets depending on the specified file" "R --no-builtin-variables; disable the build-in variables and rules" "v --version; print version info" "W: --what-if: --new-file: --assume-new:; assume the specified file has just been modified" "--warn-undefined-variables; print an error message when an undefined variable is encountered" ) #<# ;; (*BSD) ADDOPTIONS=("$ADDOPTIONS" #># "B; disable POSIX-incompatible optimization" "d:; specify debug flags" "m; specify a directory to add to the system include path" "V:; print the value of the specified variable" ) #<# case $type in (FreeBSD) ADDOPTIONS=("$ADDOPTIONS" #># "E:; specify a macro overridden by environment variable" "P; don't intermix output of concurrent jobs" "Q; be extra quiet" "v; be extra verbose" "X; don't expand variable values recursively (with -V)" "x:; specify warning options" ) #<# ;; (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "N; don't execute any commands at all" "T:; specify a file to save a trace record" "W; treat warnings as errors" "X; export variables by MAKEFLAGS instead of environment variables" ) #<# ;; esac ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "D; print makefile contents" "d; print why make chooses to rebuild a target" "K:; specify a state file" "P; print defined macros and targets completely" "V; run in SysV mode" "x; enable compatibility mode" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "B; disable compatibility mode" "b; enable compatibility mode" "P; enable parallel command execution" "w; suppress warnings" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([fKoTW]|--file|--makefile|--old-file|--assume-old|--what-if|--new-file|--assume-new) complete -P "$PREFIX" -f ;; ([CIm]|--directory|--include-dir) complete -P "$PREFIX" -S / -T -d ;; ([DEV]) command -f completion/make::macro ;; # (d) # #TODO # ;; # (j|--jobs) # ;; # (l|--load-average|--max-load) # ;; # (x) # #TODO # ;; (*) command -f completion//getoperands command -f completion/make::operand ;; esac } function completion/make::macro { complete -P "$PREFIX" "$@" -v while read -rA targets; do complete -P "$PREFIX" "$@" -- "$targets" done 2>/dev/null <(make -pq | sed -n ' /^[[:space:]]*#/d /^ /d /:/d /=/ { s/=.*$// p } ') } function completion/make::operand { typeset allowmacro=true case $type in (GNU) ;; (*) typeset word for word in "$WORDS"; do case $word in (*=*) ;; (*) allowmacro=false ;; esac done ;; esac if $allowmacro; then command -f completion/make::macrooperand fi command -f completion/make::target } function completion/make::macrooperand { case $TARGETWORD in (*=*) typeset word="${TARGETWORD#*=}" typeset PREFIX="${TARGETWORD%"$word"}" complete -P "$PREFIX" -f ;; (*) command -f completion/make::macro -S = -T ;; esac } function completion/make::target { typeset completed=false while read -rA targets; do for target in "$targets"; do case $target in (.*|*[/%]*) ;; (*) complete -P "$PREFIX" -- "$target" completed=true ;; esac done done 2>/dev/null <(make -pq MAKE=: | sed -n ' /^[[:space:]]*#/d /^ /d /=/d /:/ { s/:.*$// p } ') if ! $completed; then complete -P "$PREFIX" -f fi } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/man000066400000000000000000000105331354143602500163720ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "man" command. # Supports POSIX 2008, man-db 2.5.7, and other conventional syntaxes. function completion/man { if "${WORDS[1]}" --where man >/dev/null 2>&1; then typeset type=man-db else typeset type=POSIX fi case $type in (man-db) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a ${long:+--all}; show all manual pages, not only the first found one" "f ${long:+--whatis}; only print brief description" "k ${long:+--apropos}; search manual database and print brief description" "M: ${long:+--manpath:}; specify directories containing manual pages" "s: ${long:+--sections:}; specify manual sections" ) #<# case $type in (man-db) OPTIONS=("$OPTIONS" #># "7 --ascii; display ASCII translation of certain Latin-1 characters" "C: --config-file:; specify a pathname of the config file" "D --default; reset options (possibly set by \$MANOPT)" "e: --extension:; only search sections with the specified suffix" "H:: --html::; view the manual as HTML with the specified browser" "h --help; print help" "I --match-case; case-sensitive search" "i --ignore-case; case-insensitive search" "K --global-apropos; search for text in all manual pages" "L: --locale:; specify locale" "l --local-file; treat operands as manual page filenames" "m: --systems:; specify system names to show the manual for" "P: --pager:; specify a pager to show the manual" "p: --preprocessor:; specify a preprocessor sequence" "R: --recode:; specify an encoding to convert the manual page to" "r: --prompt:; specify a prompt string used in the less pager" "T:: --troff-device::; use groff with the specified device" "t --troff; use groff -mandoc to format pages" "u --update; check cache consistency" "V --version; print version info" "w --where --location; print the pathname of the manual page" "W --where-cat --location-cat; print the pathname of the cat file" "X:: --gxditview::; view the manual using gxditview with the specified DPI" "Z --ditroff; force groff to produce ditroff" "--names-only; match page names only (with --regex or --wildcard)" "--no-justification --nj; disable justification of text" "--no-hyphenation --nh; disable automatic hyphenation" "--no-subpages; don't try subpages like git-diff for \`git-diff'" "--regex; use regular expression in searching" "--warnings::; specify groff warning types to enable" "--wildcard; use shell-like pattern matching in searching" ) #<# esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (C|--config-file) complete -P "$PREFIX" -f ;; (H|--html) complete -P "$PREFIX" --external-command ;; (M|--manpath) typeset targetword="${TARGETWORD#"$PREFIX"}" targetword=${targetword##*:} PREFIX=${TARGETWORD%"$targetword"} complete -P "$PREFIX" -S / -T -d ;; ('') case $TARGETWORD in (*/*) complete -f ;; (*) command -f completion/man::operand ;; esac ;; esac } function completion/man::operand { typeset i=1 sections= manpath= while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) if [ $((i+1)) -le ${WORDS[#]} ]; then case ${WORDS[i+1]} in ([[:digit:]]*) sections=${WORDS[i+1]} esac fi break ;; (-M*) manpath=${WORDS[i]#-M} ;; (-s*) sections=${WORDS[i]#-s} ;; esac i=$((i+1)) done IFS=':,' sections=($sections) [ "$manpath" ] || manpath=$(manpath 2>/dev/null) || manpath=${MANPATH:-/usr/man:/usr/share/man:/usr/local/man:/usr/local/share/man} IFS=: manpath=($manpath) IFS= typeset saveopts=$(set +o) set +o noglob -o nullglob +o nocaseglob typeset mandir section for mandir in "$manpath"; do if [ ${sections[#]} -gt 0 ]; then for section in "$sections"; do command -f completion/man::operand::search "${mandir}/man${section}" command -f completion/man::operand::search "${mandir}/sman${section}" command -f completion/man::operand::search "${mandir}/man${section}.Z" done else for mandir in "$mandir"/man* "$mandir"/sman*; do command -f completion/man::operand::search "${mandir}" done fi done eval "$saveopts" } function completion/man::operand::search { typeset files files=("$1"/*) files=("${${${${${${files##*/}%.[gx]z}%.bz2}%.lzma}%.Z}%.*}") complete -R '' -- "$files" } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/mesg000066400000000000000000000004521354143602500165510ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "mesg" command. # Supports POSIX 2008. function completion/mesg if [ ${WORDS[#]} -eq 1 ]; then #>># complete -D "allow messages from other uses" y complete -D "deny messages from other uses" n fi #<<# # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/mkdir000066400000000000000000000024451354143602500167300ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "mkdir" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/mkdir { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "m: ${long:+--mode:}; specify the permission of the new directory" "p ${long:+--parents}; make parent directories if necessary" ) #<# case $type in (GNU|FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "v ${long:+--verbose}; print a message for each directory processed" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "Z: --context:; specify the security context of the new directory" "--help" "--version" ) #<# esac esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (m|--mode) if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode mkdir fi ;; (Z|--context) ;; (*) complete -S / -T -d ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/mkfifo000066400000000000000000000022401354143602500170660ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "mkfifo" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/mkfifo { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "m: ${long:+--mode:}; specify the permission of the new FIFO" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "Z: --context:; specify the security context of the new FIFO" "--help" "--version" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "p; make parent directories if necessary" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (m|--mode) if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode mkfifo fi ;; (Z|--context) ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/mksh000066400000000000000000000002621354143602500165570ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "mksh" command. function completion/mksh { command -f completion//reexecute sh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/more000066400000000000000000000033671354143602500165700ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "more" command. # Supports POSIX 2008, SunOS 5.10, HP-UX 11i v3. # (We don't support the util-linux-ng version because it's far from POSIX- # compliance.) function completion/more { case $("${WORDS[1]}" --version 2>/dev/null) in (*'less'*) command -f completion//reexecute less return ;; esac typeset type="$(uname 2>/dev/null)" typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c; don't scroll the screen" "e; exit immediately after printing the last line of the last file" "i; case-insensitive search" "n:; specify the number of lines in the screen" "p:; specify a command executed after loading each file" "s; squeeze adjacent empty lines into one" "t:; specify an identifier to jump" "u; disable special treatment of backspace" ) #<# case $type in (SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "d; display more user-friendly prompt" "f; don't wrap long lines" ) #<# esac case $type in (SunOS) OPTIONS=("$OPTIONS" #># "l; don't pause at form feeds" "w; don't exit immediately after printing the last line of the file" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "v; don't print unprintable characters like ^I or M-C" "W:; specify an additional option" "x:; specify the width of a tab" "z; print backspace, tab, line feed as ^H, ^I, ^M" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (t) if [ -r tags ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 tags 2>/dev/null) fi ;; (W) #>># complete -P "$PREFIX" -D "don't initialize the screen" notite complete -P "$PREFIX" -D "initialize the screen" tite ;; #<<# ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/mv000066400000000000000000000043161354143602500162430ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "mv" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.3, SunOS 5.10, HP-UX 11i v3. function completion/mv { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f ${long:+--force}; overwrite existing files without prompt" "i ${long:+--interactive}; ask before overwriting existing files" ) #<# case $type in (GNU|FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# case $type in (GNU|FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "n ${long:+--no-clobber}; don't overwrite existing files" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "b; like --backup=existing" "--backup::; specify how to make a backup" "--strip-trailing-slashes; remove trailing slashes from source operands" "S: --suffix:; specify a suffix to append to backup file names" "T --no-target-directory; always treat the destination as a regular file" "t: --target-directory:; specify a destination directory" "u --update; don't move if destination is newer than source" "--help" "--version" ) #<# esac esac ;; (HP-UX) OPTIONS=("$OPTIONS" #># "e:; specify extent attributes treatment" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--backup) if command -vf completion//completebackup >/dev/null 2>&1 || . -AL completion/_backup; then command -f completion//completebackup fi ;; (e) #>># complete -P "$PREFIX" -D "print a message when failed to preserve an extent attribute" warn complete -P "$PREFIX" -D "don't preserve extent attributes" ignore complete -P "$PREFIX" -D "don't move a file when cannot preserve the extent attribute" force ;; #<<# (S|--suffix) ;; (t|--target-directory) complete -T -P "$PREFIX" -S / -d ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/nawk000066400000000000000000000002631354143602500165560ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "nawk" command. function completion/nawk { command -f completion//reexecute awk } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/newgrp000066400000000000000000000006611354143602500171220ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "newgrp" command. # Supports POSIX 2008. function completion/newgrp { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "l; reset environment variables, etc. as if you logged in again" ) #<# command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -g ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/nice000066400000000000000000000015711354143602500165370ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "nice" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/nice { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "n: ${long:+--adjustment:}; specify the nice value increment" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands command -f completion//reexecute -e ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/nl000066400000000000000000000042251354143602500162310ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "nl" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/nl { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "b: ${long:+--body-numbering:}; specify the numbering style of body lines" "d: ${long:+--section-delimiter:}; specify section delimiter characters" "f: ${long:+--footer-numbering:}; specify the numbering style of footer lines" "h: ${long:+--header-numbering:}; specify the numbering style of header lines" "i: ${long:+--line-increment:}; specify the line number increment" "l: ${long:+--join-blank-lines:}; consider n adjacent empty lines as one" "n: ${long:+--number-format:}; specify the line numbering format" "p ${long:+--no-renumber}; don't reset the line number at the beginning of pages" "s: ${long:+--number-separator:}; specify the separator between the line number and the text" "v: ${long:+--starting-line-number:}; specify the initial line number" "w: ${long:+--number-width:}; specify the number of characters for the line number" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([bfh]|--body-numbering|--footer-numbering|--header-numbering) #>># complete -P "$PREFIX" -D "number all lines" a complete -P "$PREFIX" -D "number nonempty lines" t complete -P "$PREFIX" -D "number no lines" n complete -P "$PREFIX" -D "number lines matching the following regular expression" -T p ;; #<<# (n|--number-format) #>># complete -P "$PREFIX" -D "left justified, leading zeros suppressed" ln complete -P "$PREFIX" -D "right justified, leading zeros suppressed" rn complete -P "$PREFIX" -D "right justified, leading zeros kept" rz ;; #<<# ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/nohup000066400000000000000000000031601354143602500167460ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "nohup" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/nohup { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=() case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# ;; (SunOS) OPTIONS=("$OPTIONS" #># "a; override signal handlers set by the target processes themselves" "F; grab control of target processes controlled by another process" "g; make running process groups immune to SIGHUP" "p; make running processes immune to SIGHUP" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') typeset word pid args for word in "${WORDS[2,-1]}"; do case $word in (-p) # complete a process ID while read -r pid args; do if kill -n 0 $pid; then complete -D "$args" -- "$pid" fi done 2>/dev/null <(ps -a -o pid= -o args=) break ;; (-g) # complete a process group ID while read -r pid args; do if kill -n 0 -$pid; then complete -D "$args" -- "$pid" fi done 2>/dev/null <(ps -a -o pgid= -o args=) break ;; (--) command -f completion//getoperands command -f completion//reexecute -e break ;; esac done ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/od000066400000000000000000000114361354143602500162240ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "od" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/od { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac case $type in (GNU|*BSD|Darwin) typeset bsd=true ;; (*) typeset bsd= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "A: ${long:+--address-radix:}; specify the radix of addresses printed at the beginning of lines" "b; like -t o1: interpret bytes in octal" "c; interpret bytes as characters" "d; like -t u2: interpret 2-byte words in unsigned decimal" "j: ${long:+--skip-bytes:}; specify the number of bytes to skip first" "N: ${long:+--read-bytes:}; specify the number of bytes to dump at most" "o ${bsd:+B}; like -t o2: interpret 2-byte words in octal" "s; like -t d2: interpret 2-byte words in signed decimal" "t: ${long:+--format:}; specify output format" "v ${long:+--output-duplicates}; don't omit lines containing the same data as the previous line" "x ${bsd:+h}; like -t x2: interpret 2-byte words in hexadecimal" ) #<# ADDOPTIONS=() case $type in (GNU|*BSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "F ${bsd:+e}; like -t fD: interpret double-precision floating point numbers" "f; like -t fF: interpret single-precision floating point numbers" "O; like -t o4: interpret 4-byte words in octal" "X ${bsd:+H}; like -t x4: interpret 4-byte words in hexadecimal" ) #<# case $type in (GNU|*BSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "a; like -t a: interpret named characters" "l L I; like -t dL: interpret signed long values in decimal" "i; like -t dI: interpret singed int values in decimal" ) #<# esac case $type in (GNU|FreeBSD|Darwin|SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "D; like -t u4: interpret 4-byte words in unsigned decimal" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "S: --strings::; output strings of at least n-byte long" "w:: --width::; specify the number of input bytes per output line" "--traditional; use the traditional syntax" "--help" "--version" ) #<# ;; (SunOS) ADDOPTIONS=("$ADDOPTIONS" #># "C; interpret bytes as characters" "S; like -t d4: interpret 4-byte words in signed decimal" ) #<# ;; esac esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (A|--address-radix) #>># complete -P "$PREFIX" -D "decimal" d complete -P "$PREFIX" -D "octal" o complete -P "$PREFIX" -D "hexadecimal" x complete -P "$PREFIX" -D "none" n ;; #<<# (j|--skip-bytes) typeset word hex command -f completion/od::size && { if command -vf completion//completesizesuffix >/dev/null 2>&1 || . -AL completion/_blocksize; then command -f completion//completesizesuffix k m ${hex:-b} fi } ;; ([NS]|--read-bytes|--strings) typeset word hex command -f completion/od::size ;; (w|--width) ;; (t|--format) #>># complete -P "$TARGETWORD" -D "named character" a complete -P "$TARGETWORD" -D "character" c complete -P "$TARGETWORD" -D "signed decimal" d complete -P "$TARGETWORD" -D "floating point number" f complete -P "$TARGETWORD" -D "octal" o complete -P "$TARGETWORD" -D "unsigned decimal" u complete -P "$TARGETWORD" -D "hexadecimal" x #<<# case ${TARGETWORD#"$PREFIX"} in (*[doux]) #>># complete -P "$TARGETWORD" -D "char" C complete -P "$TARGETWORD" -D "short" S complete -P "$TARGETWORD" -D "int" I complete -P "$TARGETWORD" -D "long" L ;; #<<# (*f) #>># complete -P "$TARGETWORD" -D "float" F complete -P "$TARGETWORD" -D "double" D complete -P "$TARGETWORD" -D "long double" L ;; #<<# esac case $type in (GNU) case ${TARGETWORD#"$PREFIX"} in (*[acdfouxCDFSIL[:digit:]]) #>># complete -P "$TARGETWORD" -D "also print characters directly" z esac #<<# esac ;; (*) complete -f ;; esac } # This function sets the word, hex, PREFIX variables. function completion/od::size { word="${TARGETWORD#"$PREFIX"}" case $word in (0[Xx]*) hex=true ;; (*) hex= ;; esac case $word in ([[:digit:]]*) while [ "${word#[[:digit:]AaBbCcDdEeFfXx]}" != "$word" ]; do word=${word#[[:digit:]AaBbCcDdEeFfXx]} done PREFIX=${TARGETWORD%"$word"} case $type in (GNU) if command -vf completion//completesizesuffix >/dev/null 2>&1 || . -AL completion/_blocksize; then command -f completion//completesizesuffix GNU${hex:+-E} fi esac return 0 esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/passwd000066400000000000000000000010751354143602500171210ustar00rootroot00000000000000# (C) 2015 magicant # Completion script for the "passwd" command. function completion/passwd { # Although some options seem common among different Unix-like systems, # I don't find a reliable way to exactly detect which options are # supported on the current system. So, no options are supported in this # script now. typeset OPTIONS ARGOPT PREFIX OPTIONS=() command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -P "$PREFIX" -u ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/paste000066400000000000000000000023331354143602500167320ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "paste" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/paste { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "d: ${long:+--delimiters:}; specify delimiter characters" "s ${long:+--serial}; concatenate all lines in each input file" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (d|--delimiters) typeset word="${TARGETWORD#"$PREFIX"}" word=${word//\\\\} case $word in (*\\*) PREFIX=${TARGETWORD%\\*} #>># complete -T -P "$PREFIX" -D "empty string" '\0' complete -T -P "$PREFIX" -D "newline" '\n' complete -T -P "$PREFIX" -D "tab" '\t' complete -T -P "$PREFIX" -D "backslash" '\\' esac #<<# ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/patch000066400000000000000000000111521354143602500167140ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "patch" command. # Supports POSIX 2008, GNU patch 2.6.1, SunOS 5.10, HP-UX 11i v3. function completion/patch { if "${WORDS[1]}" --version >/dev/null 2>&1; then typeset type=GNU else typeset type="$(uname 2>/dev/null)" fi case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "b ${long:+--backup}; back up files before patching" "c ${long:+--context}; assume copied context format" "D: ${long:+--ifdef:}; output in merged #ifdef format with the specified conditional macro name" "d: ${long:+--directory:}; specify a directory to work in" "e ${long:+--ed}; assume ed script format" "i: ${long:+--input:}; specify the patch file" "l ${long:+--ignore-whitespace}; tolerate whitespace mismatches" "N ${long:+--forward}; ignore patches that seem reversed or already applied" "n ${long:+--normal}; assume normal diff format" "o: ${long:+--output:}; specify the result file name" "p: ${long:+--strip:}; specify the number of pathname components to strip from file names" "R ${long:+--reverse}; patch in reverse" "r: ${long:+-reject-file:}; specify the reject file name" "u ${long:+--unified}; assume unified context format" ) #<# ADDOPTIONS=() case $type in (GNU|HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "F: ${long:+--fuzz:}; specify the number of lines in context that may mismatch" "f ${long:+--force}; don't ask anything to the user" "s ${long:+--silent --quiet}; suppress informative messages" "v ${long:+--version}; print version info" ) #<# esac case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "--backup-if-mismatch; back up a file if the patch doesn't match the file exactly" "--no-backup-if-mismatch; don't back up a file even if the patch doesn't match the file exactly" "B: --prefix:; specify a backup file pathname prefix" "--binary; don't convert CR-LF into LF" "--dry-run; don't modify files actually but print messages as usual" "E --remove-empty-files; remove empty result files" "g: --get:; specify what to do when a file is missing or read-only" "--merge::; leave conflicts in the result file like diff3 and merge" "--posix; disable non-POSIX extensions" "--quoting-style:; specify the quoting style for output names" "--reject-format:; specify the reject file format" "T --set-time; set the time stamp of result files using local time" "t --batch; don't ask anything to the user" "V: --version-control:; specify how to make a backup" "--verbose; print extra informative messages" "Y: --basename-prefix:; make backups in subdirectories with the specified name" "Z --set-utc; set the time stamp of result files using UTC" "z: --suffix:; make backups in the same directory with the specified suffix" "--help" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "S; ignore this patch and process the next patch" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS case $type in (HP-UX) typeset i="${WORDS[#]}" while [ $i -gt 1 ]; do if [ "${WORDS[i]}" = "+" ]; then WORDS=("${WORDS[1]}" "${WORDS[i+1,-1]}") break fi i=$((i-1)) done esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (D|--ifdef) if [ -r tags ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 tags 2>/dev/null) fi ;; (d|--directory) complete -T -P "$PREFIX" -S / -d ;; (g|--get) #>># complete -P "$PREFIX" -D "check out the missing file" 1 complete -P "$PREFIX" -D "do nothing" 0 complete -P "$PREFIX" -D "ask whether to check out the file" -- -1 ;; #<<# (--merge) #>># complete -P "$PREFIX" -D "contains original lines from the patch" diff3 complete -P "$PREFIX" -D "doesn't contain original lines from the patch" merge ;; #<<# (--quoting-style) #>># complete -P "$PREFIX" -D "no quotation" literal complete -P "$PREFIX" -D "quotation suitable for the shell" shell complete -P "$PREFIX" -D "always quote in the shell style" shell-always complete -P "$PREFIX" -D "C string literal" c complete -P "$PREFIX" -D "C string literal without surrounding double-quotes" escape ;; #<<# (--reject-format) #>># complete -P "$PREFIX" -D "copied context format" context complete -P "$PREFIX" -D "unified context format" unified ;; #<<# (V|--version-control) if command -vf completion//completebackup >/dev/null 2>&1 || . -AL completion/_backup; then command -f completion//completebackup fi ;; ([Fp]|--fuzz|--strip) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/pathchk000066400000000000000000000014741354143602500172450ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "pathchk" command. # Supports POSIX 2008, GNU coreutils 8.4. function completion/pathchk { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type=POSIX;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "P; check for empty pathnames and leading hyphens" "p; check pathname length limits and character sets" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--portability; like -Pp: full check" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/pgawk000066400000000000000000000002651354143602500167310ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "pgawk" command. function completion/pgawk { command -f completion//reexecute awk } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ping000066400000000000000000000016611354143602500165560ustar00rootroot00000000000000# (C) 2018 magicant # Completion script for the "ping" command. # Supports common options from iputils s20180629, FreeBSD 11.2, OpenBSD 6.3, # NetBSD 7.1, SunOS 5.11. function completion/ping { typeset type="$(uname 2>/dev/null)" typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "d; use an SO_DEBUG socket" "n; don't resolve the host name" "R; record route" "v; verbose" ) #<# case $type in (Linux|*BSD) OPTIONS=("$OPTIONS" #># "c:; specify the number of packets to send" "f; flood ping" "I:; specify a source address" "i:; specify an interval between packets sent" "L; suppress multicast loopback" "l:; specify the number of initial packets sent at once" "p:; specify padding bytes" "q; quiet" "s:; specify the number of data bytes in a packet" ) #<# esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/popd000066400000000000000000000002661354143602500165630ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "popd" built-in command. function completion/popd { command -f completion//reexecute cd } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/pr000066400000000000000000000060141354143602500162370ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "pr" command. # Supports POSIX 2008, GNU coreutils 8.4, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/pr { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS POSIXOPTIONS ADDOPTIONS ARGOPT PREFIX POSIXOPTIONS=( #># "a ${long:+--across}; arrange lines across columns" "d ${long:+--double-space}; double space" "e:: ${long:+--expand-tabs::}; expand tabs into spaces" "F f ${long:+--form-feed}; use form feeds to separate pages" "h: ${long:+--header:}; specify the header string" "i:: ${long:+--output-tabs::}; replace adjacent spaces with tabs" "l: ${long:+--length:}; specify the number of lines per page" "m ${long:+--merge}; print all files in parallel, each in one column" "n:: ${long:+--number-lines::}; print line numbers" "o: ${long:+--indent:}; specify the width of offset preceding each line" "p; pause before printing each page to the terminal" "r ${long:+--no-file-warnings}; don't warn about missing/unreadable files" "s:: ${long:+--separator::}; specify the column separator" "t ${long:+--omit-header}; omit page headers and footers" "w: ${long:+--width:}; specify the line width for multi-column layout" ) #<# ADDOPTIONS=() case $type in (GNU) ADDOPTIONS=("$ADDOPTIONS" #># "c --show-control-chars; make unprintable characters visible by caret notation and backslashed octal notation" "D: --date-format:; specify the date format in the header" "J --join-lines; merge full lines" "N: --first-line-number:; specify the number to start counting line numbers from" "S:: --sep-string::; specify a string to separate columns" "T --omit-pagination; like -t, and eliminate form feeds in the input" "v --show-nonprinting; make unprintable characters visible by backslashed octal notation" "W: --page-width:; specify the line width" "--help" "--version" ) #<# ;; (FreeBSD|Darwin) ADDOPTIONS=("$ADDOPTIONS" #># "L:; specify the locale" ) #<# ;; (NetBSD) ADDOPTIONS=("$ADDOPTIONS" #># "T:; specify the date format in the header" ) #<# ;; (HP-UX) ADDOPTIONS=("$ADDOPTIONS" #># "c:; specify the number of columns" ) #<# ;; esac OPTIONS=("$POSIXOPTIONS" "$ADDOPTIONS") unset POSIXOPTIONS ADDOPTIONS WORDS=("${WORDS/#+/-}") command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([DT]|--date-format) if command -vf completion//completestrftime >/dev/null 2>&1 || . -AL completion/date; then command -f completion//completestrftime fi ;; (L) IFS=" " complete -P "$PREFIX" -- $(locale -a 2>/dev/null) ;; ([ceilNnosWw]|--expand-tabs|--output-tabs|--first-line-number|--length|--number-lines|--indent|--separator|--width|--page-width) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/printf000066400000000000000000000050631354143602500171230ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "printf" built-in command. function completion/printf { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion//getoperands if [ ${WORDS[#]} -eq 0 ]; then command -f completion/printf::backslash printf || command -f completion/printf::percent else complete -f fi ;; esac } function completion/printf::backslash { typeset word="$TARGETWORD" word=${word//\\\\} case $word in (*\\) PREFIX=${TARGETWORD%\\} #>># complete -T -P "$PREFIX" -D "alert (bell)" '\a' complete -T -P "$PREFIX" -D "backspace" '\b' complete -T -P "$PREFIX" -D "form feed" '\f' complete -T -P "$PREFIX" -D "newline" '\n' complete -T -P "$PREFIX" -D "carriage return" '\r' complete -T -P "$PREFIX" -D "tab" '\t' complete -T -P "$PREFIX" -D "vertical tab" '\v' complete -T -P "$PREFIX" -D "backslash" '\\' #<<# case ${1-} in (printf) #>># complete -T -P "$PREFIX" -D "double-quote" '\"' complete -T -P "$PREFIX" -D "single-quote" '\'"'" ;; #<<# (echo) #>># complete -T -P "$PREFIX" -D "stop printing" '\c' ;; #<<# esac return 0 esac return 1 } function completion/printf::percent { typeset word="$TARGETWORD" word=${word//%%} case $word in (*%) PREFIX=${TARGETWORD%\%} #>># complete -T -P "$PREFIX" -D "signed decimal integer" '%d' complete -T -P "$PREFIX" -D "signed decimal integer" '%i' complete -T -P "$PREFIX" -D "unsigned decimal integer" '%u' complete -T -P "$PREFIX" -D "unsigned octal integer" '%o' complete -T -P "$PREFIX" -D "unsigned hexadecimal integer (lowercase)" '%x' complete -T -P "$PREFIX" -D "unsigned hexadecimal integer (uppercase)" '%X' complete -T -P "$PREFIX" -D "floating-point number (lowercase)" '%f' complete -T -P "$PREFIX" -D "floating-point number (uppercase)" '%F' complete -T -P "$PREFIX" -D "floating-point number with exponent (lowercase)" '%e' complete -T -P "$PREFIX" -D "floating-point number with exponent (uppercase)" '%E' complete -T -P "$PREFIX" -D "%f or %e (automatically selected)" '%g' complete -T -P "$PREFIX" -D "%F or %E (automatically selected)" '%G' complete -T -P "$PREFIX" -D "first character of a string" '%c' complete -T -P "$PREFIX" -D "string" '%s' complete -T -P "$PREFIX" -D "string (escape sequences allowed)" '%b' complete -T -P "$PREFIX" -D "%" '%%' return 0 #<<# esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ps000066400000000000000000000164641354143602500162520ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ps" command. # Supports POSIX 2008, procps 3.2.8, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/ps { typeset -x PS_PERSONALITY=posix case $("${WORDS[1]}" --version 2>/dev/null) in (*'procps'*) typeset type=procps ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (procps) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A; print all processes" "a; print all processes with terminals" "G: ${long:+--Group:}; specify real group IDs of processes to print" "o: ${long:+--format:}; specify format items to print" "p: ${long:+--pid:}; specify process IDs of processes to print" "t: ${long:+--tty:}; specify terminals of processes to print" "U: ${long:+--User:}; specify real user IDs of processes to print" ) #<# case $type in (procps|*BSD|Darwin|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "j; print job info" "l; print in the long format" ) #<# case $type in (procps|Darwin|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "d; print all processes but session leaders" "e; print all processes" "f; print in the full format" "u: ${long:+--user:}; specify effective user IDs of processes to print" ) #<# case $type in (procps) OPTIONS=("$OPTIONS" #># "g:; specify session IDs or effective group IDs of processes to print" "--group:; specify effective group IDs of processes to print" ) #<# ;; (*) OPTIONS=("$OPTIONS" #># "g:; specify process group IDs of processes to print" ) #<# ;; esac esac esac case $type in (procps) OPTIONS=("$OPTIONS" #># "F; print in the extra full format" "m; print threads after each process" "N --deselect; print processes not selected by other options" "n:; specify the name list file" "T; print threads rather than processes" "Z M --context; print security contexts" "V --version; print version info" "--columns: --cols: --width:; specify the screen width" "--cumulative; include child processes' CPU time in their parent's" "--headers; print a header line for each screen page" "--info; print debugging info" "--lines: --rows:; specify the screen height" "--no-headers; don't print the header line" "--ppid:; specify parent process IDs of processes to print" "--sort:; specify sort keys" "--help" ) #<# esac case $type in (procps|*BSD|Darwin) OPTIONS=("$OPTIONS" #># "O:; specify format items to print in addition to the default ones" "w; wide format" ) #<# case $type in (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "C; use the raw CPU percentage" "c; print only the command name for the command column" "L; print available format items" "m; sort by memory usage" "N:; specify the name list file" "r; sort by current CPU usage" "S; include child processes' CPU time in their parent's" "T; print processes associated with the current terminal" "v; print in the virtual memory format" "x; print processes with terminals as well" ) #<# case $type in (FreeBSD) OPTIONS=("$OPTIONS" #># "d; print process family trees" "f; print data even for swapped-out processes" "H; print thread info" "X; don't print processes with terminals" "Z; print MAC labels as well" ) #<# ;; (OpenBSD) OPTIONS=("$OPTIONS" #># "k; print thread info" "W:; specify a file to extract swap info from" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "k:; specify sort keys" "s; print threads rather than processes" "W:; specify a file to extract swap info from" ) #<# ;; esac case $type in (Darwin) OPTIONS=("$OPTIONS" #># "E; print environment variables as well" "M; print thread info" "X; don't print processes with terminals" ) #<# ;; (*) OPTIONS=("$OPTIONS" #># "e; print environment variables as well" "M:; specify a core file to print" "u; print in the user-oriented format" ) #<# ;; esac esac esac case $type in (procps|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "s: ${long:+--sid:}; specify session IDs of processes to print" ) #<# case $type in (procps|SunOS) OPTIONS=("$OPTIONS" #># "L; print thread info" "P; print running processor numbers (PSR) as well" "y; print RSS instead of F and ADDR (with -l)" ) #<# case $type in (SunOS) OPTIONS=("$OPTIONS" #># "Z; print zone info as well" "z:; specify zones of processes to print" ) #<# esac esac case $type in (procps|HP-UX) OPTIONS=("$OPTIONS" #># "C:; specify the name of process to print" "H ${long:+--forest}; print process family trees" "P; print process resource manager (PRM) info as well" ) #<# case $type in (HP-UX) OPTIONS=("$OPTIONS" #># "R:; specify PRM process group IDs of processes to print" "x; print in the extended format" "Z:; specify processor set IDs of processes to print" ) #<# esac esac esac command -f completion//parseoptions -e case $ARGOPT in (-) command -f completion//completeoptions ;; (C) typeset word="${TARGETWORD#"$PREFIX"}" PREFIX=${TARGETWORD%"${word##*[ ,]}"} complete -P "$PREFIX" -- $(ps -A -o comm= 2>/dev/null) ;; (G|--[Gg]roup) complete -P "$PREFIX" -g ;; (g) typeset word="${TARGETWORD#"$PREFIX"}" PREFIX=${TARGETWORD%"${word##*[ ,]}"} case $type in (procps) case $word in (*[![:digit:]\ ,]*) ;; (*) # complete a session ID typeset sid tty while read -r sid tty; do complete -P "$PREFIX" -R 0 -D "$tty" -- "$sid" done 2>/dev/null <(ps -A -o sid= -o tty= | sort -u) ;; esac complete -P "$PREFIX" -g ;; (*) ;; esac ;; ([MNnW]) complete -P "$PREFIX" -f ;; ([kOo]|--format|--sort) # complete a format item typeset word="${TARGETWORD#"$PREFIX"}" PREFIX=${TARGETWORD%"${word##*[ ,+-]}"} case $type in (procps) typeset key name while read -r key name; do complete -P "$PREFIX" -- "$key" done <(ps L) ;; (*BSD|Darwin) complete -P "$PREFIX" -- $(ps -L 2>/dev/null) ;; (*) case $type in (SunOS) complete -P "$PREFIX" -- addr class ctid gid f \ fname lwp nlwp osz pmem pri project \ projid pset psr rgid rss ruid s sid \ stime taskid uid wchan zone zoneid ;; (HP-UX) complete -P "$PREFIX" -- addr cls cpu flags \ gid pri prmgrp prmid pset rgid ruid \ sid state stime sz uid wchan ;; esac complete -P "$PREFIX" -- args comm etime group nice \ pcpu pgid pid ppid rgroup ruser time tty user vsz ;; esac ;; (p|--pid|--ppid) # complete a process ID typeset pid args while read -r pid args; do complete -P "$PREFIX" -D "$args" -- "$pid" done <(ps -A -o pid= -o args= 2>/dev/null) ;; (R) #TODO ;; (s|--sid) # complete a session ID typeset sid tty while read -r sid tty; do complete -P "$PREFIX" -R 0 -D "$tty" -- "$sid" done 2>/dev/null <(ps -A -o sid= -o tty= | sort -u) ;; (t|--tty) typeset ttys ttys=($(ps -A -o tty=)) complete -P "$PREFIX" -A '*[[:alnum:]]*' -- "$ttys" "${ttys#tty}" ;; ([Uu]|--[Uu]ser) complete -P "$PREFIX" -u ;; (Z) #TODO ;; (z) #TODO ;; (--columns|--cols|--width|--lines|--rows) ;; (*) command -f completion/ps::bsd ;; esac } # parse BSD style options function completion/ps::bsd { #TODO } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/pushd000066400000000000000000000002701354143602500167370ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "pushd" built-in command. function completion/pushd { command -f completion//reexecute cd } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/pwd000066400000000000000000000006671354143602500164200ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "pwd" built-in command. function completion/pwd { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "L --logical; keep symbolic links in the \$PWD variable as is" "P --physical; print path without symbolic links" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/read000066400000000000000000000011271354143602500165310ustar00rootroot00000000000000# (C) 2010-2015 magicant # Completion script for the "read" built-in command. function completion/read { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A --array; assign words to an array" "e --line-editing; use line-editing" "P --ps1; use \$PS1 as a prompt" "p: --prompt:; specify a prompt" "r --raw-mode; don't treat backslashes as escapes" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (p|--prompt) complete -P "$PREFIX" -fv ;; (*) complete -v ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/readonly000066400000000000000000000003031354143602500174260ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "readonly" built-in command. function completion/readonly { command -f completion//reexecute typeset } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/renice000066400000000000000000000022601354143602500170620ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "renice" command. # Supports POSIX 2008 and the traditional syntax. function completion/renice { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "g; treat operands as process group IDs" "n:; specify the nice value increment" "p; treat operands as process IDs" "u; treat operands as user names" ) #<# command -f completion//parseoptions -e case $ARGOPT in (-) command -f completion//completeoptions ;; ('') typeset type=pid arg for arg in "${WORDS[2,-1]}"; do case $arg in (-p|--pid) type=pid ;; (-g|--pgrp) type=pgrp ;; (-u|--user) type=user ;; esac done case $type in (pid) # complete a process ID typeset pid args while read -r pid args; do if kill -n 0 $pid; then complete -D "$args" -- "$pid" fi done 2>/dev/null <(ps -A -o pid= -o args=) ;; (pgrp) # complete a process group ID typeset pid args while read -r pid args; do if kill -n 0 -$pid; then complete -D "$args" -- "$pid" fi done 2>/dev/null <(ps -A -o pgid= -o args=) ;; (user) # complete a user name complete -u ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/return000066400000000000000000000006201354143602500171320ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "return" built-in command. function completion/return { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "n --no-return; don't return; just return the specified exit status" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rgview000066400000000000000000000003061354143602500171170ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rgview" command. # Supports Vim 7.3. function completion/rgview { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rgvim000066400000000000000000000003041354143602500167360ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rgvim" command. # Supports Vim 7.3. function completion/rgvim { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rm000066400000000000000000000037431354143602500162420ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rm" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/rm { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f ${long:+--force}; remove files without confirmation" "i; confirm before removing each file" "R r ${long:+--recursive}; recursively remove directories" ) #<# case $type in (GNU|*BSD|Darwin) OPTIONS=("$OPTIONS" #># "d; try to remove directories like other files" ) #<# case $type in (GNU|FreeBSD) OPTIONS=("$OPTIONS" #># "I; confirm before removing many files" ) #<# esac case $type in (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "P; clear file contents before removing" ) #<# case $type in (FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "W; undelete files" ) #<# esac esac case $type in (GNU|FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "v ${long:+--verbose}; print a message for each file processed" ) #<# esac esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--interactive::; specify when to confirm removal" "--one-file-system; skip subdirectories on different file systems" "--no-preserve-root; don't treat the root directory specially" "--preserve-root; don't remove the root directory" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (--interactive) #>># complete -P "$PREFIX" -D "don't confirm removal" never complete -P "$PREFIX" -D "confirm before removing many files" once complete -P "$PREFIX" -D "confirm before removing each file" always ;; #<<# (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rmdir000066400000000000000000000024321354143602500167330ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rmdir" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/rmdir { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "p ${long:+--parents}; remove parent directories as well" ) #<# case $type in (GNU|FreeBSD) OPTIONS=("$OPTIONS" #># "v ${long:+--verbose}; print a message for each directory processed" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--ignore-fail-on-non-empty; suppress error messages about non-empty directories" "--help" "--version" ) #<# ;; (SunOS) OPTIONS=("$OPTIONS" #># "s; suppress error messages (with -p)" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "f; remove directories without confirmation" "i; confirm before removing each directory" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -T -S / -d ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rsync000066400000000000000000000201341354143602500167530ustar00rootroot00000000000000# (C) 2013 magicant # Completion script for the "rsync" command. # Supports rsync 3.0.9. function completion/rsync { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "0 --from0; treat filename list files as null-separated" "4 --ipv4; prefer IPv4" "6 --ipv6; prefer IPv6" "8 --8-bit-output; don't escape non-ASCII characters" "A --acls; like -P but preserve ACL as well" "--address:; specify an address to bind to" "--append; copy files by appending only" "--append-verify; like --append but verify whole files" "a --archive; like -Dgloprt" "b --backup; make a backup of existing destination files" "--backup-dir:; specify where to make a backup" "--blocking-io; use blocking IO" "B: --block-size:; specify the block size used in delta-transfer" "--bwlimit:; specify a speed limit in kilobytes/second" "c --checksum; use checksum to decide if a file needs to be sent" "--checksum-seed:; specify a seed for checksum" "--chmod:; specify permissions of copied files" "--compare-dest:; specify a directory to compare with as destination" "--compress-level:; specify the compression level" "--config:; specify a config file for daemon" "--contimeout:; specify timeout for rsync connection daemon" "--copy-dest:; like --compare-dest but copy from if possible" "--copy-unsafe-links; follow symbolic links to outside of source tree" "C --cvs-exclude; exclude files like CVS does" "D; like --devices --specials" "--daemon; run as a daemon" "--delay-updates; replace destination files after all transfer" "--devices; copy character and block device files" "d --dirs; copy directories but not their contents" "--exclude:; skip files whose names match the specified pattern" "--exclude-from:; skip files whose names match a pattern in the specified file" "E --executability; preserve file executability" "--existing --ignore-non-existing; copy only files existing on destination" "e: --rsh:; specify a program for remote connection" "F; like --filter='dir-merge /.rsync-filter'" "--fake-super; use extended attributes to preserve various attributes" "--files-from:; specify a file containing filenames to transfer" "f: --filter:; specify an exclusion rule" "--force; allow replacing non-empty directories with non-directories" "g --group; preserve file owner groups" "H --hard-links; preserve hard links" "h --human-readable; print size using K, M, etc." "--iconv:; specify filename encodings" "--ignore-existing; don't copy files existing on destination" "I --ignore-times; don't skip files that match size and time" "--include:; copy only files whose names match the specified pattern" "--include-from:; copy only files whose names match a pattern in the specified file" "--inplace; modify destination files in-place" "i --itemize-changes; print a summarized list of changes" "K --keep-dirlinks; follow symbolic links to directories on destination side" "k --copy-dirlinks; follow symbolic links to directories on source side" "L --copy-links; follow all symbolic links in source files" "--link-dest:; specify a directory to search for hard-linkable files" "l --links; copy symbolic links as symbolic links" "--list-only; list files without actual transfer" "--log-file:; specify a file to print log to" "--log-file-format:; specify a format used in logging with --log-file" "m --prune-empty-dirs; don't copy empty directories" "--max-delete:; specify the maximum number of files deleted" "--max-size:; specify the maximum size of files copied" "--min-size:; specify the minimum size of files copied" "--modify-window:; specify max time difference treated as same" "n --dry-run; don't actually copy files" "--no-detach; don't run in the background (with --daemon)" "--no-motd; don't print the message of the day sent from daemon" "--no-implied-dirs; don't copy intermediate directory paths (with -R)" "--no-inc-recursive --no-i-r; disable incremental recursion" "--numeric-ids; match numeric owner/group IDs rather than names" "O --omit-dir-times; don't preserve modification times for directories" "--only-write-batch:; like --write-batch, but no actual transfer" "--out-format:; specify a format printed for each file update" "o --owner; preserve file owners" "P; like --partial --progress" "--partial; leave partially copied files if interrupted" "--partial-dir:; specify a directory where intermediate files are kept" "--password-file:; specify a file containing a password" "p --perms; preserve file permissions" "--port:; specify a port for daemon connection" "--progress; show progress during transfer" "--protocol:; specify a protocol version" "q --quiet; print error messages only" "--read-batch:; specify a batch file containing transfer recipe" "R --relative; preserve all pathname components from source" "r --recursive; copy directories recursively" "--rsync-path:; specify a program executed on the remote host" "s --protect-args; protect filenames from word splitting by the remote shell" "--safe-links; ignore symbolic links to outside of source tree" "--size-only; skip files that match in size" "--skip-compress:; specify a list of suffixes that are not compressed" "--sockopts:; specify socket options" "S --sparse; handle sparse files efficiently" "--specials; copy named sockets, pipes, etc." "--stats; print transfer statistics" "--suffix:; specify a suffix to append to backup file names" "--super; exercise superuser rights to preserve various attributes" "T: --temp-dir:; specify a directory for temporary files on destination side" "--timeout:; specify timeout in seconds" "t --times; preserve file modification times" "u --update; don't copy if destination is newer than source" "v --verbose; print detail during execution" "W --whole-file; send whole files rather than just different parts" "--write-batch:; specify a file where transfer recipe is recorded" "X --xattrs; preserve extended attributes" "x --one-file-system; skip subdirectories on different file systems" "y --fuzzy; look for similar files to optimize transfer" "z --compress; enable compression" "--help" "--version" ) #<# # TODO --no-xxx option command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--*-batch|--config|--exclude*|--files-from|--include*|--log-file|--password-file) complete -P "$PREFIX" -f ;; (--address) complete -P "$PREFIX" -h ;; # (--backup-dir) # ;; (B|--block-size) #TODO ;; # (--bwlimit) # ;; # (--checksum-seed) # ;; (--chmod) if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode rsync fi ;; # (--*-dest) # # How can we complete remote directory names before the remote # # host name is entered on the command line? # ;; # (--compress-level) # ;; # (--contimeout) # ;; (e|--rsh) complete -P "$PREFIX" --external-command ;; (f|--filter) #TODO ;; (--*-format) #TODO ;; (--iconv) typeset word="${TARGETWORD#"$PREFIX"}" word=${word#*,} PREFIX="${TARGETWORD%"$word"}" if command -vf completion/iconv::compenc >/dev/null 2>&1 || . -AL completion/iconv; then command -f completion/iconv::compenc fi ;; # (--max-delete) # ;; (--max-size|--min-size) if command -vf completion//prefixdigits >/dev/null 2>&1 || . -AL completion/_blocksize; then if command -f completion//prefixdigits; then command -f completion//completesizesuffix KMGB fi fi ;; # (--modify-window) # ;; # (--partial-dir) # # How can we complete remote directory names before the remote # # host name is entered on the command line? # ;; # (--port) # ;; # (--protocol) # ;; # (--rsync-path) # ;; # (--skip-compress) # ;; # (--sockopts) # ;; # (--suffix) # ;; # (T|--temp-dir) # # How can we complete remote directory names before the remote # # host name is entered on the command line? # ;; # (--timeout) # ;; ('') command -f completion/rsync::operand ;; esac } function completion/rsync::operand { #TODO support rsync daemon protocol command -f completion//getoperands WORDS=(rsync "$WORDS") if command -vf completion/scp::operand >/dev/null 2>&1 || . -AL completion/ssh; then command -f completion/scp::operand fi } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rview000066400000000000000000000003041354143602500167460ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rview" command. # Supports Vim 7.3. function completion/rview { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/rvim000066400000000000000000000003021354143602500165650ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "rvim" command. # Supports Vim 7.3. function completion/rvim { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/scp000066400000000000000000000002541354143602500164030ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "scp" command. function completion/scp { command -f completion//reexecute ssh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sed000066400000000000000000000037531354143602500164000ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "sed" command. # Supports POSIX 2008, GNU sed 4.2.1, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/sed { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU sed'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "e: ${long:+--expression:}; specify a sed script directly" "f: ${long:+--file:}; specify a file containing sed script" "n ${long:+--quiet --silent}; suppress default output" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "b --binary; don't convert CR-LF into LF" "i:: --in-place::; modify files in-place, making backups with the specified filename suffixes" "l: --line-length:; specify the line length for the l command" "r --regexp-extended; use extended regular expression" "s --separate; treat each operand file separately" "u --unbuffered; disable input/output buffering" "--follow-symlinks; follow symbolic links in operands (with -i)" "--posix; disable non-POSIX extensions" "--help" "--version" ) #<# ;; (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "a; open files for the w command only when needed" "E; use extended regular expression" ) #<# case $type in (FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "i:; modify files in-place, making backups with the specified filename suffixes" "l; disable output buffering" ) #<# case $type in (FreeBSD) OPTIONS=("$OPTIONS" #># "I:; like -i, but don't treat input files separately" ) #<# esac ;; (OpenBSD) OPTIONS=("$OPTIONS" #># "u; disable output buffering" ) #<# ;; esac ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (e|--expression) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/set000066400000000000000000000231211354143602500164070ustar00rootroot00000000000000# (C) 2011-2019 magicant # Completion script for the "set" built-in command. # Completion function "completion/set" is used for the "ksh" and "yash" # commands as well. Supports AT&T ksh 20100621, yash 2.47. function completion/set { typeset prog="${WORDS[1]##*/}" typeset OPTIONS SOPTIONS LOPTIONS OPTIONS=( #># "o:; specify an option" "--help" ) #<# SOPTIONS=() LOPTIONS=() case $prog in (yash) OPTIONS=("$OPTIONS" #># "--noprofile; don't read the profile file" "--norcfile; don't read the yashrc file" "--profile:; specify the profile file" "--rcfile:; specify the yashrc file" "V --version; print version info" ) #<# ;; (ksh) OPTIONS=("$OPTIONS" #># "D --dump-strings; extract translatable strings" "R:; create a cross reference database in the specified file" "--version" ) #<# ;; esac command -f completion/set::getopt "$prog" typeset i=2 ARGOPT PREFIX while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--|-|++) complete -f return ;; (++*) case $prog in (set|yash) ;; (*) complete -f return ;; esac ;; (-*|+?*) command -f completion/set::checkoption "${WORDS[i]}" false case $ARGOPT in (f) i=$((i+1)) if [ $i -gt ${WORDS[#]} ]; then complete -P "$PREFIX" -f return fi ;; (o) i=$((i+1)) if [ $i -gt ${WORDS[#]} ]; then command -f completion/set::completelongoption return fi ;; esac ;; (*) complete -f return ;; esac i=$((i+1)) done case $TARGETWORD in ([+-]*) command -f completion/set::checkoption "$TARGETWORD" true case $ARGOPT in (s) command -f completion/set::completeshortoption ;; (o) command -f completion/set::completelongoption ;; (*) complete -P "$PREFIX" -f ;; esac ;; (*) complete -f ;; esac } function completion/set::checkoption { case $1 in (--*|++*) command -f completion/set::checklongoption "$@" ;; (*) command -f completion/set::checkshortoption "$@" ;; esac } function completion/set::checkshortoption { typeset word="${1#[+-]}" while [ "$word" ]; do for opt in ${OPTIONS%%;*}; do case $opt in (?:) case ${word[1]} in ("${opt%:}") case $opt in (o:) ARGOPT=o ;; (*) ARGOPT=f ;; esac if $2; then PREFIX=${1%"${word#?}"} elif [ "${word#?}" ]; then ARGOPT= PREFIX= else PREFIX= fi return esac esac done word=${word#?} done if $2; then ARGOPT=s PREFIX=$1 else ARGOPT= PREFIX= fi } function completion/set::checklongoption { case $prog in (ksh) case $1 in (++*) ARGOPT= PREFIX= return esac esac typeset MATCHES opt word name MATCHES=() word=${1#[+-][+-]} name=${word%%=*} for opt in ${OPTIONS%%;*}; do case $opt in (--*) case ${opt%:} in ("--$name"*) MATCHES=("$MATCHES" "$opt") esac esac done case $prog in (set|yash) name=$(tr -Cd \[:alnum:] <<<$word 2>/dev/null) ;; (ksh) name=$(tr -d _- <<<$word 2>/dev/null) ;; (*) name=$word ;; esac for opt in ${LOPTIONS%%;*}; do for opt in $opt no$opt; do case $opt in ("$name"*) MATCHES=("$MATCHES" "$opt") esac done done if [ ${MATCHES[#]} -eq 1 ]; then case ${MATCHES[1]} in (--*:) case $word in (*=*) if $2; then ARGOPT=f PREFIX=${1%"${1#*=}"} return fi ;; (*) if $2; then ARGOPT=o PREFIX= else ARGOPT=f PREFIX= fi return ;; esac esac fi if $2; then ARGOPT=o PREFIX=${1[1,2]} else ARGOPT= PREFIX= fi } function completion/set::getopt { SOPTIONS=( #># "a; export all variables when assigned" "b; print job status immediately when done" "C; disallow >-redirection to overwrite an existing file" "e; exit immediately when a command's exit status is non-zero" "f; disable pathname expansion (globbing)" "m; enable job control" "n; don't execute any commands" "u; disallow expansion of undefined variables" "v; echo commands entered to the shell" "x; print a command line before executing it" ) #<# LOPTIONS=( #># "allexport; export all variables when assigned" "braceexpand; enable brace expansion" "clobber; allow >-redirection to overwrite an existing file" "emacs; emacs-like line-editing" "errexit; exit immediately when a command's exit status is non-zero" "exec; actually execute commands" "glob; enable pathname expansion (globbing)" "ignoreeof; don't exit when an end-of-file is entered" "markdirs; append a slash to directory names after pathname expansion" "monitor; enable job control" "notify; print job status immediately when done" "unset; allow expansion of undefined variables" "verbose; echo commands entered to the shell" "vi; vi-like line-editing" "xtrace; print a command line before executing it" ) #<# case $prog in (set|yash) SOPTIONS=("$SOPTIONS" #># "h; cache full paths of commands in a function when defined" ) #<# LOPTIONS=("$LOPTIONS" #># "caseglob; make pathname expansion case-sensitive" "curasync; a newly-executed background job becomes the current job" "curbg; a background job becomes the current job when resumed" "curstop; a background job becomes the current job when stopped" "dotglob; don't treat a period at the beginning of a filename specially" "emptylastfield; don't remove empty last field in field splitting" "errreturn; return immediately when a command's exit status is non-zero" "extendedglob; enable recursive pathname expansion" "forlocal; make the iteration variable local in a for loop" "hashondef; cache full paths of commands in a function when defined" "histspace; don't save a command starting with a space in the history" "leconvmeta; always treat meta-key flags in line-editing" "lenoconvmeta; never treat meta-key flags in line-editing" "lepredict; suggest a command fragment while line-editing" "lepredictempty; suggest a command fragment on empty input while line-editing" "levisiblebell; alert with a flash, not a bell" "lepromptsp; ensure the prompt is printed at the beginning of a line" "lealwaysrp; always show the right prompt during line-editing" "lecompdebug; print debugging info during command line completion" "notifyle; print job status immediately when done while line-editing" "nullglob; remove words that matched nothing in pathname expansion" "pipefail; fail the whole pipeline if any of its commands fails" "posix; force strict POSIX conformance" "traceall; print trace of auxiliary commands" ) #<# ;; (ksh) SOPTIONS=("$SOPTIONS" #># "G; enable recursive pathname expansion" "H; enable !-expansion on the command line" "h; cache full paths of commands when entered" "k; allow assignments in the middle of command arguments" "p; work in the privileged mode" "r; work in the restricted mode" "t; execute one command only" ) #<# LOPTIONS=("$LOPTIONS" #># "bgnice; run background jobs at lower priorities" "gmacs; gmacs-like line-editing" "globstar; enable recursive pathname expansion" "histexpand; enable !-expansion on the command line" "keyword; allow assignments in the middle of command arguments" "multiline; allow multiple line editing" "pipefail; return last non-zero exit status of commands in a pipe" "privileged; work in the privileged mode" "restricted; work in the restricted mode" "showme; trace commands preceded by a semicolon" "trackall; cache full paths of commands when entered" "viraw; vi-like line-editing without canonical input handling" ) #<# ;; esac case $prog in (yash|ksh) SOPTIONS=("$SOPTIONS" #># "c; execute the first operand as a shell script" "i; work in the interactive mode" "l; work as a login shell" "s; read commands from the standard input" ) #<# LOPTIONS=("$LOPTIONS" #># "interactive; work in the interactive mode" "login; work as a login shell" ) #<# case $prog in (yash) LOPTIONS=("$LOPTIONS" #># "cmdline; execute the command specified on the command line" "stdin; read commands from the standard input" ) #<# ;; (ksh) SOPTIONS=("$SOPTIONS" #># "E; read the \$ENV file on shell invocation" ) #<# LOPTIONS=("$LOPTIONS" #># "rc; read the \$ENV file on shell invocation" ) #<# ;; esac esac } function completion/set::completeshortoption { typeset opt desc for opt in "$OPTIONS" "$SOPTIONS"; do command -f completion/set::getdesc for opt in ${opt%%;*}; do opt=${opt%:} case $opt in (?) complete -O -P "$PREFIX" ${desc:+-D "$desc"} -- "$opt" esac done done } function completion/set::completelongoption { typeset opt desc part normpart case $TARGETWORD in (--*) for opt in "$OPTIONS"; do command -f completion/set::getdesc for opt in ${opt%%;*}; do case $opt in (--*:) complete -T -P -- ${desc:+-D "$desc"} -- "${{opt#--}%:}=" ;; (--*) complete -P -- ${desc:+-D "$desc"} -- "${opt#--}" ;; esac done done esac part=${TARGETWORD#"$PREFIX"} case $prog in (set|yash) normpart=$(tr -Cd \[:alnum:] <<<$part 2>/dev/null) ;; (ksh) normpart=$(tr -d _- <<<$part 2>/dev/null) ;; (*) normpart=$part ;; esac for opt in "$LOPTIONS"; do command -f completion/set::getdesc opt=${opt%%;*} for opt in $opt no$opt; do case $opt in ("$normpart"*) complete -P "$PREFIX" ${desc:+-D "$desc"} -- "$part${opt#"$normpart"}" esac done done } function completion/set::getdesc { case $opt in (*\;*) desc=${opt#*;} while true; do # trim surrounding spaces case $desc in ([[:space:]]*) desc=${desc#[[:space:]]} ;; (*[[:space:]]) desc=${desc%[[:space:]]} ;; (*) break ;; esac done ;; (*) desc= ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sftp000066400000000000000000000002561354143602500165740ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "sftp" command. function completion/sftp { command -f completion//reexecute ssh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sh000066400000000000000000000145771354143602500162450ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "sh" built-in command. # Completion function "completion/sh" is used for the "bash", "dash" and "mksh" # commands as well. Supports POSIX 2008, bash 4.1, dash 0.5.5.1, mksh R39c. function completion/sh { typeset compoptopts= typeset prog="${WORDS[1]##*/}" typeset OPTIONS FOPTIONS OOPTIONS ARGOPT PREFIX FOPTIONS=( #># "c; execute the first operand as a shell script" "i; work in the interactive mode" "o:; specify an option" ) #<# OOPTIONS=( #># "a --allexport; export all variables when assigned" "b --notify; print job status immediately when done" "C --noclobber; disallow >-redirection to overwrite an existing file" "e --errexit; exit immediately when a command's exit status is non-zero" "f --noglob; disable pathname expansion (globbing)" "m --monitor; enable job control" "n --noexec; don't execute any commands" "u --nounset; disallow expansion of undefined variables" "v --verbose; echo commands entered to the shell" "x --xtrace; print a command line before executing it" "--ignoreeof; don't exit when an end-of-file is entered" "--vi; vi-like line-editing" ) #<# case $prog in (bash) OOPTIONS=("$OOPTIONS" #># "h --hashall; cache full paths of commands in a function when defined" ) #<# ;; (mksh) OOPTIONS=("$OOPTIONS" #># "h --trackall; cache full paths of commands when entered" ) #<# ;; (*) OOPTIONS=("$OOPTIONS" #># "h; cache full paths of commands in a function when defined" ) #<# ;; esac case $prog in (bash) FOPTIONS=("$FOPTIONS" #># "l --login; work as a login shell" ) #<# ;; (dash) FOPTIONS=("$FOPTIONS" #># "l; work as a login shell" ) #<# ;; (mksh) OOPTIONS=("$OOPTIONS" #># "l --login; work as a login shell" ) #<# ;; esac case $prog in (mksh) OOPTIONS=("$OOPTIONS" #># "s --stdin; read commands from the standard input" ) #<# ;; (*) FOPTIONS=("$FOPTIONS" #># "s; read commands from the standard input" ) #<# ;; esac case $prog in ([bd]ash|mksh) OOPTIONS=("$OOPTIONS" #># "--emacs; emacs-like line-editing" ) #<# esac case $prog in (bash|mksh) OOPTIONS=("$OOPTIONS" #># "--posix; force strict POSIX conformance" "p --privileged; work in the privileged mode" "P --physical; make the -P option the default in the cd commands etc." ) #<# esac case $prog in (bash) FOPTIONS=("$FOPTIONS" #># "--help" "--version" "--noprofile; don't read profile file" "--debugger; enable extended debugging mode" "--dump-po-strings; extract translatable strings in po file format" "D --dump-strings; extract translatable strings" "--noediting; don't use the Readline library" "--norc; don't read rc file" "--rcfile: --init-file:; specify the rc file" "r --restricted; work in the restricted mode" "--rpm-requires; print package names required to run the script" "O:; specify a shopt option" ) #<# OOPTIONS=("$OOPTIONS" #># "H --histexpand; enable !-expansion on the command line" "k --keyword; allow assignments in the middle of command arguments" "--pipefail; return last non-zero exit status of commands in a pipe" "B --braceexpand; enable brace expansion" "E --errtrace; don't reset ERR trap in subshells" "T --functrace; don't reset DEBUG and RETURN traps in subshells" "t --onecmd; execute one command only" "--history; enable command history" ) #<# compoptopts="$compoptopts -e" esac case $prog in (mksh) OOPTIONS=("$OOPTIONS" #># "--braceexpand; enable brace expansion" "r --restricted; work in the restricted mode" "--bgnice; run background jobs at lower priorities" "--gmacs; gmacs-like line-editing" "U --utf8-mode; enable UTF-8 support" "X --markdirs; append a slash to directory names after pathname expansion" "--arc4random; use arc4random for random number generation" "--nohup; don't kill running jobs when exiting" "--sh; force very strict POSIX conformance" "--vi-esccomplete; use the Escape key for completion in the vi mode" "--vi-tabcomplete; use the Tab key for completion in the vi mode" ) #<# esac OPTIONS=("$FOPTIONS" "$OOPTIONS") # convert plus-prefixed options into hyphen-prefixed options typeset i=2 while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (-|--) break ;; ([+-]*o) i=$((i+2)) ;; ([+-]?*) i=$((i+1)) ;; (*) break ;; esac done WORDS=("${WORDS[1]}" "${WORDS[2,i-1]/#+/-}" "${WORDS[i,-1]}") # the same for $TARGETWORD typeset SAVETARGETWORD="$TARGETWORD" if [ $i -gt ${WORDS[#]} ]; then TARGETWORD=${TARGETWORD/#+/-} fi command -f completion//parseoptions case $ARGOPT in (-) case $prog in (set|yash|ksh) ;; (*) OPTIONS=("$OOPTIONS") command -f completion/set::removelongopts OPTIONS=("$FOPTIONS" "$OPTIONS") ;; esac case $SAVETARGETWORD in (-*) command -f completion//completeoptions $compoptopts ;; (+*) TARGETWORD=$SAVETARGETWORD command -f completion/set::option short ;; esac ;; (o) PREFIX=${SAVETARGETWORD%"${TARGETWORD#"$PREFIX"}"} TARGETWORD=$SAVETARGETWORD OPTIONS=("$OOPTIONS") command -f completion/set::option long ;; (O) typeset shopt value while read -r shopt value; do complete -P "$PREFIX" -- "$shopt" done 2>/dev/null <(bash -O /dev/null function completion/set::option { if [ "${1-}" != long ]; then typeset single=true else typeset single=false fi typeset opts desc typeset generated=false for opts in "$OPTIONS"; do # get description case $opts in (*\;*) desc=${opts#*;} while true; do # trim surrounding spaces case $desc in ([[:space:]]*) desc=${desc#[[:space:]]} ;; (*[[:space:]]) desc=${desc%[[:space:]]} ;; (*) break ;; esac done ;; (*) desc= ;; esac if $single; then # generate single-character option for opt in ${opts%%;*}; do case $opt in ([[:alnum:]]|[[:alnum:]]:) complete -O -P "$TARGETWORD" \ ${desc:+-D "$desc"} "${opt%:}" && generated=true break esac done else # generate candidate for first match for opt in ${opts%%;*}; do case $opt in (--*) complete -P "$PREFIX" \ ${desc:+-D "$desc"} \ -- "${opt#--}" && generated=true && break esac done fi done $generated } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/shift000066400000000000000000000007001354143602500167270ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "shift" built-in command. function completion/shift { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A: --array:; specify an array name to remove elements from" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (A|--array) complete -P "$PREFIX" --array-variable ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/slogin000066400000000000000000000002621354143602500171100ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "slogin" command. function completion/slogin { command -f completion//reexecute ssh } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sort000066400000000000000000000117741354143602500166160ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "sort" command. # Supports POSIX 2008, GNU coreutils 8.6, OpenBSD 4.8, NetBSD 5.0, # SunOS 5.10, HP-UX 11i v3. function completion/sort { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "b ${long:+--ignore-leading-blanks}; ignore leading blanks in sort keys" "C; like -c, but don't report disorders" "c; check if the input is well-sorted" "d ${long:+--dictionary-order}; consider blank or alphanumeric characters only" "f ${long:+--ignore-case}; case-insensitive comparison" "i ${long:+--ignore-nonprinting}; consider printable characters only" "k: ${long:+--key:}; specify the sort field" "m ${long:+--merge}; assume all input files are already sorted" "n ${long:+--numeric-sort}; compare numeric values" "o: ${long:+--output:}; specify the output file" "r ${long:+--reverse}; sort in reverse order" "t: ${long:+--field-separator:}; specify the field separator character" "u ${long:+--unique}; make equal lines unique" ) #<# case $type in (GNU|*BSD|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "T: ${long:+--temporary-directory:}; specify the directory to put temporary files in" ) #<# case $type in (GNU|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "M ${long:+--month-sort}; compare month names" ) #<# case $type in (GNU|SunOS) OPTIONS=("$OPTIONS" #># "S: ${long:+--buffer-size:}; specify the initial buffer size in 1024 byte units" ) #<# esac esac case $type in (GNU|*BSD) OPTIONS=("$OPTIONS" #># "s ${long:+--stable}; stable sort" ) #<# case $type in (GNU|OpenBSD) OPTIONS=("$OPTIONS" #># "z ${long:+--zero-terminated}; use null bytes as the line separator" ) #<# esac case $type in (*BSD) OPTIONS=("$OPTIONS" #># "R:; specify the line separator" ) #<# esac esac esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--batch-size:; specify the number of inputs to sort at once" "--check::; check if the input is well-sorted" "--compress-program:; specify the program to compress temporary files" "--debug; print debugging messages" "--files0-from:; specify a file containing null-separated filenames to read" "g --general-numeric-sort; compare floating-point numbers" "h --human-numeric-sort; compare numeric values followed by SI suffixes" "--parallel:; specify the number of sort processes to run at a time" "R --random-sort; sort in random order" "--random-source:; specify the random seed file" "--sort:; specify comparison type" "V --version-sort; compare version numbers" "--help" "--version" ) #<# ;; (OpenBSD) OPTIONS=("$OPTIONS" #># "H; use merge sort rather than radix sort" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "S; non-stable sort" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "A; simple byte-by-byte comparison" "z:; specify the buffer size per line" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([RStz]|--batch-size|--buffer-size|--field-separator|--parallel) ;; (--check) #>># complete -P "$PREFIX" -D "don't report disorders" quiet silent complete -P "$PREFIX" -D "report disorders" diagnose-first ;; #<<# (--compress-program) WORDS=() command -f completion//reexecute -e ;; (k|--key) typeset word="${TARGETWORD#"$PREFIX"}" case $word in (*[[:alnum:]]) #>># complete -T -P "$TARGETWORD" -D "ignore leading blanks in sort keys" b complete -T -P "$TARGETWORD" -D "consider blank or alphanumeric characters only" d complete -T -P "$TARGETWORD" -D "case-insensitive comparison" f complete -T -P "$TARGETWORD" -D "consider printable characters only" i complete -T -P "$TARGETWORD" -D "compare numeric values" n complete -T -P "$TARGETWORD" -D "sort in reverse order" r #<<# case $type in (GNU|SunOS|HP-UX) #>># complete -T -P "$TARGETWORD" -D "compare month names" M esac #<<# case $type in (GNU|SunOS|HP-UX) #>># complete -T -P "$TARGETWORD" -D "compare floating-point numbers" g complete -T -P "$TARGETWORD" -D "compare numeric values followed by SI suffixes" h complete -T -P "$TARGETWORD" -D "sort in random order" R complete -T -P "$TARGETWORD" -D "compare version numbers" V esac #<<# esac ;; (--sort) #>># complete -P "$PREFIX" -D "compare floating-point numbers" general-numeric complete -P "$PREFIX" -D "compare numeric values followed by SI suffixes" human-numeric complete -P "$PREFIX" -D "compare month names" month complete -P "$PREFIX" -D "compare numeric values" numeric complete -P "$PREFIX" -D "compare version numbers" version complete -P "$PREFIX" -D "sort in random order" random complete -P "$PREFIX" -D "" ;; #<<# (T|--temporary-directory) complete -P "$PREFIX" -S / -T -d ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/split000066400000000000000000000035611354143602500167550ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "split" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/split { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a: ${long:+--suffix-length:}; specify the suffix length for output files" "b: ${long:+--bytes:}; specify the output file size in bytes" "l: ${long:+--lines:}; specify the number of lines in each output file" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "C: --line-bytes:; like -b, but split at line breaks" "d --numeric-suffixes; use numeric suffixes rather than lowercase letters" "--verbose; print a message for each output file processed" "--help" "--version" ) #<# esac case $type in (FreeBSD|NetBSD) OPTIONS=("$OPTIONS" #># "n:; specify the number of output files" ) #<# esac case $type in (Darwin|FreeBSD|OpenBSD) OPTIONS=("$OPTIONS" #># "p:; split at lines matching the specified extended regular expression" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([alnp]|--suffix-length|--lines) ;; ([bC]|--bytes|--line-bytes) if command -vf completion//prefixdigits >/dev/null 2>&1 || . -AL completion/_blocksize; then if command -f completion//prefixdigits; then command -f completion//completesizesuffix k m case $type in (GNU) command -f completion//completesizesuffix GNU b ;; (FreeBSD) command -f completion//completesizesuffix g ;; esac fi fi ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ssh000066400000000000000000000212031354143602500164100ustar00rootroot00000000000000# (C) 2010-2018 magicant # Completion script for the "ssh" command. # Completion function "completion/ssh" is used for the "scp" and "sftp" commands # as well. # Supports OpenSSH 7.7. function completion/ssh { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># #"1; use the protocol version 1 only" #"2; use the protocol version 2 only" "4; use IPv4 addresses only" "6; use IPv6 addresses only" "C; enable data compression" "c:; specify the encryption algorithm" "F:; specify the configuration file" "i:; specify the private key (identity) file" "o:; specify an option in the configuration file format" "v; print debugging messages" ) #<# case ${WORDS[1]} in (ssh) OPTIONS=("$OPTIONS" #># "A; enable authentication agent forwarding" "a; disable authentication agent forwarding" "B:; specify the network interface to connect from" "b:; specify the local IP address to connect from" "D:; specify a port for local dynamic application-level port forwarding" "E:; specify the log file" "e:; specify the escape character" "f; run in the background after authentication" "G; print the configuration" "g; allow remote hosts to connect to local forwarded ports" "I:; specify the PKCS#11 shared library" "J:; specify jump hosts" "K; enable GSSAPI-based authentication and forwarding" "k; disable GSSAPI-based authentication and forwarding" "L:; specify a local port and a remote host/port to forward" "l:; specify the user name to log in as" "M; run in the master mode for connection sharing" "m:; specify MACs (message authentication codes)" "N; don't run any remote command" "n; redirect the standard input of the remote command to /dev/null" "O:; specify a command to control the master process" "p:; specify the port to connect to" "Q:; specify the feature to query" "q; suppress warning and diagnostic messages" "R:; specify a remote port and a local host/port to forward" "S:; specify the control socket for connection sharing" "s; run the command as the SSH2 subsystem" "T; run the command without a pseudo-terminal" "t; run the command in a pseudo-terminal" "V; print version info" "W:; specify a remote host/port to directly connect to" "w:; specify the tunnel device to forward" "X; enable X11 forwarding" "x; disable X11 forwarding" "Y; enable trusted X11 forwarding" "y; use syslog for logging" ) #<# ;; (scp) OPTIONS=("$OPTIONS" #># "3; copy via local host between remote source and destination" "B; batch mode: don't ask for passwords/phrases" ) #<# ;; (sftp) OPTIONS=("$OPTIONS" #># "a; continue on interruption" "B:; specify the buffer size in bytes" "b:; batch mode: specify the file to read commands from" "D:; specify the path of local sftp server to connect to" "R:; specify the max number of outstanding requests" "s:; specify the SSH2 subsystem or the path for the remote sftp server" ) #<# ;; (*) return 1 ;; esac case ${WORDS[1]} in (scp|sftp) OPTIONS=("$OPTIONS" #># "l:; specify the max bandwidth in Kbps" "P:; specify the port to connect to" "p; preserve file attributes" "q; suppress progress bar and warning and diagnostic messages" "r; recursively copy directories" "S:; specify the connection program used instead of ssh" ) #<# esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([BDPp]) ;; (b) case ${WORDS[1]} in (sftp) complete -P "$PREFIX" -f ;; esac ;; ([Fi]) complete -P "$PREFIX" -f ;; (c) #TODO ;; (e) #>># complete -P "$PREFIX" '~' complete -P "$PREFIX" -D "none" none ;; #<<# (I) #TODO ;; (J) PREFIX=${TARGETWORD%"${${TARGETWORD#"$PREFIX"}##*,}"} command -f completion/${WORDS[1]}::operand ;; (L) #TODO ;; (l) case ${WORDS[1]} in (ssh) complete -P "$PREFIX" -u ;; esac ;; (m) #TODO ;; (O) #>># complete -P "$PREFIX" -D "request the master process to stop port forwarding" cancel complete -P "$PREFIX" -D "check that the master process is running" check complete -P "$PREFIX" -D "request the master process to exit" exit complete -P "$PREFIX" -D "request forwarding without command execution" forward complete -P "$PREFIX" -D "request the master process to accept no more connections" stop ;; #<<# (o) #TODO ;; (Q) #>># complete -P "$PREFIX" -D "supported symmetric ciphers" cipher complete -P "$PREFIX" -D "supported symmetric ciphers that support authenticated encryption" cipher-auth complete -P "$PREFIX" -D "key exchange algorithms" kex complete -P "$PREFIX" -D "key types" key complete -P "$PREFIX" -D "certificate key types" key-cert complete -P "$PREFIX" -D "non-certificate key types" key-plain complete -P "$PREFIX" -D "supported message integrity codes" mac complete -P "$PREFIX" -D "supported SSH protocol versions" protocol-version ;; #<<# (R) #TODO ;; (S) case ${WORDS[1]} in (ssh) complete -P "$PREFIX" -f ;; (scp|sftp) WORDS=() command -f completion//reexecute -e ;; esac ;; (W) #TODO ;; (w) #TODO ;; ('') command -f completion/${WORDS[1]}::operand ;; esac } function completion/ssh::operand { typeset config ssh sshopts cmd="${WORDS[1]}" command -f completion/ssh::parseconfig if [ ${WORDS[#]} -gt 0 ]; then # complete the command WORDS=("${WORDS[2,-1]}") command -f completion//reexecute else command -f completion/ssh::completehostname fi } function completion/scp::operand { typeset config ssh sshopts cmd="${WORDS[1]}" command -f completion/ssh::parseconfig case $TARGETWORD in (${cmd}://*/*) typeset path="${TARGETWORD#"${cmd}"://*/}" command -f completion/ssh::completeremotepath ;; (${cmd}://*) command -f completion/ssh::completehostname -T -S / ;; (*:*) case ${TARGETWORD%%:*} in (*/*) # a host name cannot contain a slash command -f completion/ssh::completelocalpath ;; (*) typeset path="${TARGETWORD#*:}" command -f completion/ssh::completeremotepath ;; esac ;; (*) command -f completion/ssh::completehostname -T -S : command -f completion/ssh::completelocalpath ;; esac } function completion/sftp::operand { command -f completion/scp::operand } function completion/ssh::parseconfig { typeset i=2 config=${HOME:+"$HOME/.ssh/config"} ssh=ssh sshopts=() while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (-F*) sshopts=("$sshopts" "${WORDS[i]}") config=${WORDS[i]#-F} ;; (-[1246BCiJo]*) sshopts=("$sshopts" "${WORDS[i]}") ;; (-P*) sshopts=("$sshopts" -p"${WORDS[i]#-P}") ;; (-S*) case ${WORDS[1]} in (scp|sftp) ssh=${WORDS[1]#-S} esac ;; (--) i=$((i+1)); break ;; (-*) ;; (*) break ;; esac i=$((i+1)) done WORDS=("${WORDS[i,-1]}") } function completion/ssh::completehostname { case ${TARGETWORD#"$PREFIX"} in (${cmd}://*) PREFIX=${PREFIX}${cmd}:// esac case ${TARGETWORD#"$PREFIX"} in (*@*) PREFIX=${TARGETWORD%"${${TARGETWORD#"$PREFIX"}#*@}"} esac typeset key values file words host while read -Ar key values; do case $key in ([Hh][Oo][Ss][Tt]) complete -P "$PREFIX" -R '*[*?]*' -R '!*' "$@" -- \ "$values" esac done <(sed -e 's/#.*//' -e 's/[Hh][Oo][Ss][Tt][[:blank:]]*=/host /' "$config" 2>/dev/null) # currently, we always read known-hosts files from the default location for file in /etc/ssh/ssh_known_hosts ~/.ssh/known_hosts; do if ! [ -r "$file" ]; then continue fi while read -Ar words; do case ${words[1]} in (\#*) continue ;; (@*) words=("${words[2,-1]}") ;; esac for host in ${words[1]//,/ }; do case $host in ([\|!]*|*[*?]*) ;; (\[*]:*) complete -P "$PREFIX" "$@" -- "${{host#\[}%]:*}" ;; (*) complete -P "$PREFIX" "$@" -- "$host" ;; esac done hosts=(${words[1]//,/ }) case $key in (\|*) ;; (*) complete -P "$PREFIX" "$@" -- ${key//,/ } esac done <"$file" done } function completion/ssh::completeremotepath case $cmd in (scp|sftp|rsync) PREFIX=${TARGETWORD%"$path"} typeset host="${${PREFIX%[/:]}/#${cmd}:\/\//ssh:\/\/}" typeset name="${path##*/}" typeset dir="${path%"$name"}" typeset filter f flags if [ -o dotglob ]; then filter=() else case $name in (.*) filter=(-A '.*') ;; (*) filter=(-R '.*') ;; esac fi while read -r f; do case $f in (*/) flags='-T' ;; (*) flags= ;; esac complete -P "$PREFIX$dir" "$filter" $flags -- "$f" done <("$ssh" "$sshopts" -o BatchMode=yes -- "$host" "ls -ap -- '${${dir:-.}//\'/\'\\\'\'}'" <>/dev/null 2>&0) esac function completion/ssh::completelocalpath case $cmd in (scp|rsync) typeset slash=false case $TARGETWORD in (*/*) slash= esac complete ${slash:+-R '*:*'} -f esac # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ssh-add000066400000000000000000000015461354143602500171460ustar00rootroot00000000000000# (C) 2011-2013 magicant # Completion script for the "ssh-add" command. # Supports OpenSSH 6.2. function completion/ssh-add { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c; confirm when authenticating with the identities being added" "D; delete all identities" "d; delete identities" "e:; remove keys from the specified PKCS#11 library" "k; don't load certificates" "L; print the public keys of the identities in the agent" "l; print the fingerprints of the identities in the agent" "s:; add keys from the specified PKCS#11 library" "t:; specify the lifetime of identities being added in seconds" "X; unlock the agent" "x; lock the agent" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([est]) ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ssh-agent000066400000000000000000000013271354143602500175110ustar00rootroot00000000000000# (C) 2011-2013 magicant # Completion script for the "ssh-agent" command. # Supports OpenSSH 6.2. function completion/ssh-agent { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a:; specify the pathname of the UNIX-domain socket used to communicate with the agent" "c; output shell commands for csh" "d; debug mode" "k; kill the current agent" "s; output shell commands for sh" "t:; specify the default lifetime of identities" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (a) complete -P "$PREFIX" -f ;; (t) ;; (*) command -f completion//getoperands command -f completion//reexecute ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ssh-keygen000066400000000000000000000057141354143602500177010ustar00rootroot00000000000000# (C) 2011-2018 magicant # Completion script for the "ssh-keygen" command. # Supports OpenSSH 7.7. function completion/ssh-keygen { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A; create all types of host keys" "a:; specify the number of primality tests performed (with -T)" "B; print the bubblebabble digest" "b:; specify the number of bits of the key being created" "C:; specify the comment of the key" "c; change the comment of RSA1 private/public keys" "D:; download the RSA public keys from the specified PKCS#11 library" "E:; specify the hash algorithm for fingerprint" "e; print the key in the portable format" "F:; specify the host name to search the known hosts file for" "f:; specify the key file to operate on" "G:; specify the file to save candidate primes" "g; use generic DNS format (with -r)" "H; hash the known hosts file" "h; create a host certificate when signing a key" "I:; specify the key identity used when signing a key" "i; print the key in the non-portable format" "J:; specify a max number of lines processed (with -T)" "j:; specify the start line number (with -T)" "K:; specify the file to print the last line number to (with -T)" "k; generate a KRL file" "L; print the contents of a certificate" "l; print the fingerprint of the public key file" "M:; specify the memory size in megabytes used when generating candidate moduli" "m:; specify the key format (with -e or -i)" "N:; specify the new passphrase" "n:; specify principals included in a certificate when signing a key" "O:; specify a certificate option when signing a key" "o; use the new format to save the private key" "P:; specify the (current) passphrase" "p; change the passphrase of the private key" "Q; test if keys have been revoked in a KRL" "q; make ssh-keygen quiet" "R:; specify the host name to remove from the known hosts file" "r:; print the SSHFP fingerprint resource record of the specified host name" "S:; specify the start point for generating candidate moduli" "s:; specify the CA key file to sign the public key with" "T:; test DH group exchange candidate primes" "t:; specify the key type" "U; use a CA key in ssh-agent (with -s)" "u; update a KRL" "V:; specify the validity interval of the signed certificate" "v; print debugging messages" "W:; specify the DH generator" "y; extract the public key from the private key" "z:; specify the serial number of the certificate" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([abMSVz]) ;; (E) complete -P "$PREFIX" md5 sha256 ;; ([FR]) complete -P "$PREFIX" -h ;; (m) #>># complete -P "$PREFIX" -D 'SSH2 public or private key' RFC4716 complete -P "$PREFIX" -D 'PEM PKCS8 public key' PKCS8 complete -P "$PREFIX" -D 'PEM public key' PEM ;; #<<# (O) #TODO ;; (t) complete -P "$PREFIX" dsa ecdsa ed25519 rsa ;; (W) complete 2 3 5 ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/stty000066400000000000000000000261671354143602500166340ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "stty" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/stty { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a ${long:+--all}; print all the current settings" "g ${long:+--save}; print all the current settings in a reusable format" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "F: --file:; specify the terminal device file" "--help" "--version" ) #<# ;; (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "e; print all the current settings in the traditional BSD format" "f:; specify the terminal device file" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) case $PREFIX in ('') command -f completion//completeoptions esac ;; ([Ff]|--file) complete -P "$PREFIX" -f ;; esac case ${WORDS[-1]} in (eof|eol|eol2|erase|intr|kill|lnext|quit|start|status|stop|swtch|werase) #>># complete -T -D "control character" ^ complete -D "undefined" undef ;; #<<# (cols|columns|[io]speed|line|min|rows|time|[xy]pixels) ;; (*) case $TARGETWORD in (-*) typeset neg=- ;; (*) typeset neg= ;; esac # POSIX operands #>># complete -D "signal INTR on break" -- ${neg}brkint complete -D "backspace delay style" -- bs0 bs1 complete -D "assume a line with modem control" -- ${neg}clocal complete -D "disable raw mode settings" -- ${neg}cooked complete -D "carriage return delay style" -- cr0 cr1 cr2 cr3 complete -D "enable input receiver" -- ${neg}cread complete -D "number of bits transmitted per byte" -- cs5 cs6 cs7 cs8 complete -D "use two stop bits" -- ${neg}cstopb complete -D "echo typed characters" -- ${neg}echo complete -D "make ERASE visually erase a character" -- ${neg}echoe complete -D "echo a newline after KILL" -- ${neg}echok complete -D "always echo newline" -- ${neg}echonl complete -D "reset ERASE and KILL to the default" -- ek complete -D "specify the end of file character" -- eof complete -D "specify the end of line character" -- eol complete -D "specify the erase character" -- erase complete -D "enable even parity mode settings" -- ${neg}evenp ${neg}parity complete -D "form feed delay style" -- ff0 ff1 complete -D "hang up the connection on last close" -- ${neg}hup ${neg}hupcl complete -D "canonical input mode" -- ${neg}icanon complete -D "translate input carriage return to newline" -- ${neg}icrnl complete -D "enable extended functions" -- ${neg}iexten complete -D "ignore input break" -- ${neg}ignbrk complete -D "ignore input carriage return" -- ${neg}igncr complete -D "ignore characters with parity errors" -- ${neg}ignpar complete -D "translate input newline to carriage return" -- ${neg}inlcr complete -D "enable input parity checking" -- ${neg}inpck complete -D "specify the interrupt character" -- intr complete -D "enable INTR, QUIT, SUSP special characters" -- ${neg}isig complete -D "specify input baud rate" -- ispeed complete -D "clear 8th bit of input characters" -- ${neg}istrip complete -D "restart output on any input" -- ${neg}ixany complete -D "send STOP when input buffer is almost full" -- ${neg}ixoff complete -D "enable START/STOP special characters" -- ${neg}ixon complete -D "specify the kill character" -- kill complete -D "specify the MIN value" -- min complete -D "use newline rather than carriage return as line break" -- ${neg}nl complete -D "newline delay style" -- nl0 nl1 complete -D "don't flush after INTR, QUIT, SUSP" -- ${neg}noflsh complete -D "translate output carriage return to newline" -- ${neg}ocrnl complete -D "enable odd parity mode settings" -- ${neg}oddp complete -D "use DEL rather than NUL for fill" -- ${neg}ofdel complete -D "use fill instead of timing for delays" -- ${neg}ofill complete -D "newline works as carriage return on terminal" -- ${neg}onlret complete -D "don't print carriage returns at the first column" -- ${neg}onocr complete -D "enable output post-processing" -- ${neg}opost complete -D "specify output baud rate" -- ospeed complete -D "enable parity handling" -- ${neg}parenb complete -D "mark parity errors" -- ${neg}parmrk complete -D "select odd parity" -- ${neg}parodd complete -D "specify the quit character" -- quit complete -D "enable raw mode settings" -- ${neg}raw complete -D "reset all settings to the normal" -- sane complete -D "specify the start character" -- start complete -D "specify the stop character" -- stop complete -D "specify the suspend character" -- susp complete -D "horizontal tab delay style" -- tab0 tab1 tab2 tab3 complete -D "like tab0" -- tabs complete -D "specify the TIME value" -- time complete -D "send SIGTTOU for background output" -- ${neg}tostop complete -D "vertical tab delay style" -- vt0 vt1 #<<# if [ "$neg" ]; then complete -D "like tab3" -- -tabs fi case $type in (GNU|*BSD|Darwin|SunOS|HP-UX) #>># complete -D "specify screen width" -- columns cols complete -D "specify the delayed suspend character" -- dsusp complete -D "echo control characters in ^-notation" -- ${neg}echoctl complete -D "make KILL visually erase a line" -- ${neg}echoke complete -D "echo erased characters backwards" -- ${neg}echoprt complete -D "specify alternative end of line character" -- eol2 complete -D "specify the literal-next character" -- lnext complete -D "translate output newline to carriage return" -- ${neg}onlcr complete -D "specify screen height" -- rows complete -D "specify the word erase character" -- werase esac #<<# case $type in (GNU|*BSD|Darwin|SunOS) #>># complete -D "enable RTS/CTS flow control" -- ${neg}crtscts esac case $type in (GNU|*BSD|Darwin|HP-UX) #>># complete -D "print screen size" -- size esac #<<# case $type in (GNU|*BSD|Darwin) #>># complete -D "enable cbreak mode settings" -- cbreak complete -D "make ERASE visually erase a character" -- ${neg}crterase complete -D "make KILL visually erase a line" -- ${neg}crtkill complete -D "echo control characters in ^-notation" -- ${neg}ctlecho complete -D "set modes for DEC terminal" -- dec complete -D "like -ixany" -- ${neg}decctlq complete -D "specify the discard character" -- flush complete -D "set modes for literal output" -- ${neg}litout complete -D "set modes for 8-bit characters" -- ${neg}pass8 complete -D "echo erased characters backwards" -- ${neg}prterase complete -D "specify the reprint character" -- rprnt complete -D "send STOP when input buffer is almost full" -- ${neg}tandem esac #<<# case $type in (*BSD|Darwin|SunOS|HP-UX) #>># complete -D "assume output is discarded" -- ${neg}flusho complete -D "keep pending input characters after mode change" -- ${neg}pendin esac case $type in (*BSD|Darwin|SunOS) #>># complete -D "specify the discard character" -- discard complete -D "specify the reprint character" -- reprint esac case $type in (*BSD|Darwin) #>># complete -D "print all the current settings in the traditional BSD format" -- all everything complete -D "use alternative word erase algorithm" -- ${neg}altwerase complete -D "specify the end of line character" -- brk complete -D "set modes for a CRT" -- ${neg}crt ${neg}newcrt complete -D "make ERASE visually erase a character" -- ${neg}crtbs complete -- ${neg}extproc complete -D "enable STATUS special character" -- ${neg}kerninfo complete -D "enable CD hardware flow control on output" -- ${neg}mdmbuf complete -D "translate output tabs to spaces" -- ${neg}oxtabs complete -D "specify baud rate" -- speed complete -D "specify the status character" -- status complete -D "use the standard line discipline" -- tty new old esac #<<# case $type in (GNU|Darwin) #>># complete -D "assume input is in UTF-8" -- ${neg}iutf8 esac #<<# case $type in (FreeBSD|Darwin) #>># complete -D "specify alternative erase character" -- erase2 esac #<<# case $type in (OpenBSD|NetBSD) #>># complete -D "start output" -- ostart complete -D "stop output" -- ostop esac #<<# case $type in (GNU|OpenBSD|SunOS|HP-UX) #>># complete -D "translate input uppercase letters to lowercase" -- ${neg}iuclc complete -D "set modes for uppercase-only terminal" -- ${neg}lcase complete -D "translate output lowercase letters to uppercase" -- ${neg}olcuc complete -D "use canonical upper-/lowercase presentation" -- ${neg}xcase esac #<<# case $type in (GNU|SunOS|HP-UX) #>># complete -D "specify line discipline" -- line complete -D "specify the switch character" -- swtch esac #<<# case $type in (SunOS|HP-UX) #>># complete -D "enable CTS hardware flow control on output" -- ${neg}ctsxon complete -D "enable RTS hardware flow control on input" -- ${neg}rtsxoff complete -D "set modes for TEK terminal" -- tek complete -D "set modes for TI700 terminal" -- ti700 complete -D "set modes for TN300 terminal" -- tn300 complete -- tty33 complete -- tty37 complete -- vt05 esac #<<# case $type in (GNU) #>># complete -D "like echoe echoctl echoke" -- crt complete -D "print baud rate" -- speed ;; #<<# (OpenBSD) #>># complete -D "discard output end-of-file characters" -- ${neg}onoeot ;; #<<# (NetBSD) #>># complete -D "enable DTR/CTS flow control" -- ${neg}cdtrcts complete -D "set all modes to random values" -- insane ;; #<<# (SunOS) #>># complete -D "set normal asynchronous communication settings" -- async complete -D "enable CD hardware flow control on output" -- ${neg}cdxon complete -D "enable input hardware flow control" -- ${neg}crtsxoff complete -- ctab complete -D "set character widths for the current locale" -- ${neg}defeucw complete -D "enable DTR hardware flow control on input" -- ${neg}dtrxoff complete -D "enable isochronous hardware flow control on input" -- ${neg}isxoff complete -D "set modes for mark parity" -- ${neg}markp complete -D "enable extended parity handling" -- ${neg}parext complete -D "get receive clock from internal baud rate generator" -- rcibrg complete -D "receiver signal element timing clock not provided" -- rsetcoff complete -D "set modes for space parity" -- ${neg}spacep complete -D "use application mode on a synchronous line" -- ${neg}stappl complete -D "flush after every write on a synchronous line" -- ${neg}stflush complete -D "wrap long lines on a synchronous line" -- ${neg}stwrap complete -D "transmitter signal element timing clock not provided" -- tsetcoff complete -D "get transmit clock from internal baud rate generator" -- xcibrg complete -D "specify horizontal screen size" -- xpixels complete -D "specify vertical screen size" -- ypixels ;; #<<# (HP-UX) #>># complete -D "enable request-to-send" -- ${neg}crts complete -D "set modes for HP terminal" -- hp complete -D "enable ENQ-ACK handshaking" -- ${neg}ienqak complete -D "block output from non-current layer" -- ${neg}loblk ;; #<<# esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/su000066400000000000000000000057321354143602500162530ustar00rootroot00000000000000# (C) 2011-2016 magicant # Completion script for the "su" command. # Supports GNU coreutils 8.10, util-linux 2.28.2, FreeBSD 8.2, OpenBSD 4.9, # NetBSD 5.0, Mac OS X 10.6.4, SunOS 5.11, HP-UX 11i v3. function completion/su { case $("${WORDS[1]}" --version 2>/dev/null) in (*'util-linux'*) typeset type=util-linux gnu=true ul=true ;; (*'GNU coreutils'*) typeset type=GNU gnu=true ul= ;; (*) typeset type="$(uname 2>/dev/null)" gnu= ul= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=() case $type in (util-linux|GNU|Darwin|*BSD) OPTIONS=("$OPTIONS" #># "f ${gnu:+--fast}; pass the -f option to the shell" "l ${gnu:+--login}; invoke the command as a login shell" "m ${gnu:+p --preserve-environment}; preserve all environment variables" ) #<# case $type in (util-linux|GNU|OpenBSD) OPTIONS=("$OPTIONS" #># "s: ${gnu:+--shell:}; specify the shell to be run" ) #<# case $type in (util-linux|GNU) OPTIONS=("$OPTIONS" #># "c: --command:; specify the command run instead of the shell" "${ul:+h} --help; print help" "${ul:+V} --version; print version info" ) #<# ;; esac esac esac case $type in (*BSD) if [ "$(id -u 2>/dev/null)" -eq 0 ] 2>/dev/null; then OPTIONS=("$OPTIONS" #># "c:; specify the login class" ) #<# fi esac case $type in (util-linux) OPTIONS=("$OPTIONS" #># "g: --group:; specify the primary group to change to" "G: --supp-group:; specify supplemental group to change to" "--session-command:; like -c, but run in the same session" ) #<# ;; (FreeBSD) OPTIONS=("$OPTIONS" #># "s; set the MAC label to the default" ) #<# ;; (OpenBSD) OPTIONS=("$OPTIONS" #># "a:; specify the authentication method" "L; loop till a successful authentication" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "d; like -l, but preserve the working directory" "K; don't use Kerberos" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "d; use distributed computing environment (DCE)" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (a) if command -vf completion//bsd::completeauthmethod >/dev/null 2>&1 || . -AL completion/_bsd; then command -f completion//bsd::completeauthmethod fi ;; (c|--command) case $type in (util-linux|GNU) complete -P "$PREFIX" -c ;; (*BSD) if command -vf completion//bsd::completeauthclass >/dev/null 2>&1 || . -AL completion/_bsd; then command -f completion//bsd::completeauthclass fi ;; esac ;; ([gG]|--*group) complete -P "$PREFIX" -g ;; (s|--shell|--session-command) WORDS=() command -f completion//reexecute -e ;; (*) command -f completion//getoperands if [ "${WORDS[1]:-}" = - ]; then WORDS=("${WORDS[2,-1]}") fi if [ "${WORDS[#]}" -le 0 ]; then complete -P "$PREFIX" -u else WORDS=(sh "${WORDS[2,-1]}") #XXX assume POSIX shell command -f completion//reexecute fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sudo000066400000000000000000000053061354143602500165730ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "sudo" command. # Supports sudo 1.8.0. function completion/sudo { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A; use a GUI program for password prompt" "a:; specify the authentication method" "b; run in the background" "C:; specify the file descriptor to be closed" "c:; specify the login class" "D:; specify the debugging level" "E; preserve environment variables" "e; edit operand files" "g:; specify the group to run the command as" "H; set \$HOME to target user's home directory" "h; print help" "i; run the command as a login shell" "K; remove cached credentials entirely" "k; remove cached credentials or don't cache credentials" "l; list commands allowed for the user" "n; never prompt for the password" "P; preserve supplementary group IDs" "p:; specify the prompt string" "r:; specify the new security context's role" "S; read the password from the standard input rather than the terminal" "s; run \$SHELL" "t:; specify the new security context's type" "U:; specify the user to change from (with -l)" "u:; specify the user to change to" "V; print version info" "v; cache credentials without running a command" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (a) if command -vf completion//bsd::completeauthmethod >/dev/null 2>&1 || . -AL completion/_bsd; then command -f completion//bsd::completeauthmethod fi ;; ([CD]) ;; (c) if command -vf completion//bsd::completeauthclass >/dev/null 2>&1 || . -AL completion/_bsd; then command -f completion//bsd::completeauthclass fi ;; (g) complete -P "$PREFIX" -g ;; (p) command -f completion/sudo::prompt ;; (r) #TODO security context's role ;; (t) #TODO security context's type ;; ([Uu]) complete -P "$PREFIX" -u ;; ('') typeset i=2 edit=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i]} in (--) break;; (-e) edit=true;; esac i=$((i+1)) done if $edit; then complete -P "$PREFIX" -f else command -f completion//getoperands command -f completion//reexecute fi ;; esac } function completion/sudo::prompt { typeset word="$TARGETWORD" word=${word//%%} case $word in (*%) PREFIX=${TARGETWORD%\%} #>># complete -T -P "$PREFIX" -D "fully qualified domain name" '%H' complete -T -P "$PREFIX" -D "host name" '%h' complete -T -P "$PREFIX" -D "name of the user whose password is requested" '%p' complete -T -P "$PREFIX" -D "name of the user to run the command as" '%U' complete -T -P "$PREFIX" -D "name of the user invoking sudo" '%u' complete -T -P "$PREFIX" -D "%" '%%' return 0 #<<# esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/sudoedit000066400000000000000000000003531354143602500174360ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "sudoedit" command. # Supports sudo 1.8.0. function completion/sudoedit { WORDS=(sudo -e "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/suspend000066400000000000000000000006151354143602500173000ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "suspend" built-in command. function completion/suspend { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f --force; suppress warning about possible deadlock" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/svn000066400000000000000000000602511354143602500164270ustar00rootroot00000000000000# (C) 2011-2012 magicant # Completion script for the "svn" command. # Supports Subversion 1.7. function completion/svn { typeset OPTIONS COMMONOPTIONS ADDOPTIONS ARGOPT PREFIX COMMONOPTIONS=( #># "--config-dir:; specify a directory containing configuration files" "--config-option:; specify a configuration option" "h ? --help; print help" "--no-auth-cache; don't cache user name and password" "--non-interactive; disable interactive prompt" "--password:; specify a password for authentication" "--trust-server-cert; accept suspicious SSL server certificate" "--username:; specify a user name for authentication" ) #<# ADDOPTIONS=( #># "--accept:; specify an action for conflict resolution" "--allow-mixed-revisions" # not documented as not recommended "--auto-props; enable automatic property setting" "c: --change:; specify a change (revision)" "--changelist: --cl:; specify a changelist to operate on" "--depth:; specify directory depth to operate on" "--diff; show diff" "--diff-cmd:; specify an external program to be used as \"diff\"" "--diff3-cmd:; specify an external program to be used as \"diff3\"" "--dry-run; don't make any actual changes" "--editor-cmd:; specify an external program to be used as an editor" "--encoding:; specify the encoding of the log message" "x: --extensions:; specify arguments that is passed to the external diff command" "F: --file:; use the specified file's contents instead of invoking an editor" "--force; force operation to run" "--force-log; accept a suspicious parameter value for log messages" "--git; print in Git-like format" "--ignore-ancestry; ignore ancestry when calculating differences" "--ignore-externals; ignore externals and its working copies" "--ignore-whitespace; tolerate whitespace mismatches" "--incremental; print output in a format suitable for concatenation" "--keep-changelists; don't delete changelists after committing" "--keep-local; keep the local working copy" "l: --limit:; specify the number of log messages to be shown" "m: --message:; specify a log message" "--native-eol:; specify an end-of-line marker for native EOL settings" "--new:; specify the newer one of the compared files" "--no-auto-props; disable automatic property setting" "--no-diff-deleted; don't print diff for deleted files" "--no-ignore; don't ignore files" "--no-unlock; don't unlock files after committing" #deprecated "N --non-recursive" "--notice-ancestry; take ancestry into account when making a diff" "--old:; specify the older one of the compared files" "--parents; create nonexistent parent directories" "q --quiet; print essential information only" "--record-only; update mergeinfo without actually merging files" "R --recursive; operate on subdirectories recursively" "--reintegrate; merge a branch into the trunk" "--relocate; change the location of the repository" "--remove; remove files from a changelist" "--reverse-diff; patch in reverse" "r: --revision:; specify a revision or a revision range" "--revprop; operate on a revision property rather than a file property" "--set-depth:; specify a new sticky depth of working directories" "--show-copies-as-adds; treat copied files as newly added" "--show-revs:; specify the type of mergeinfo to print" "--show-updates; show which files will be updated by \"svn update\"" "--stop-on-copy; operate on revisions after the file was copied last" "--strict; output the raw value without pretty-formatting" "--strip:; specify the number of pathname components to strip from file names" "--summarize; print a summary of changes only" "--targets:; specify a file containing target paths" "g --use-merge-history; use mergeinfo to show history before merges" "v --verbose; print additional info" "--version" "--with-all-revprops; include all revision properties" "--with-no-revprops; include no revision properties" "--with-revprop:; specify a revision property to set or print" "--xml; print in the XML format" ) #<# OPTIONS=("$COMMONOPTIONS" "$ADDOPTIONS") command -f completion//parseoptions -es # find subcommand name typeset SUBCMD= separatorindex=2 while [ $separatorindex -lt ${WORDS[#]} ]; do case ${WORDS[separatorindex]} in (--) SUBCMD=${WORDS[separatorindex+1]} break esac separatorindex=$((separatorindex+1)) done # normalize subcommand name case $SUBCMD in (praise|annotate|ann) SUBCMD=blame;; (cl) SUBCMD=changelist;; (co) SUBCMD=checkout;; (ci) SUBCMD=commit;; (cp) SUBCMD=copy;; (del|remove|rm) SUBCMD=delete;; (di) SUBCMD=diff;; (h|\?) SUBCMD=help;; (ls) SUBCMD=list;; (mv|rename|ren) SUBCMD=move;; (pdel|pd) SUBCMD=propdel;; (pedit|pe) SUBCMD=propedit;; (pget|pg) SUBCMD=propget;; (plist|pl) SUBCMD=proplist;; (pset|ps) SUBCMD=propset;; (stat|st) SUBCMD=status;; (sw) SUBCMD=switch;; (up) SUBCMD=update;; esac case $ARGOPT in (-) OPTIONS=("$COMMONOPTIONS") if command -vf "completion/svn::$SUBCMD:opt" >/dev/null 2>&1; then command -f "completion/svn::$SUBCMD:opt" fi command -f completion//completeoptions ;; (--accept) #>># complete -P "$PREFIX" -D "discard all local and remote changes" base complete -P "$PREFIX" -D "discard all remote changes" mine-full complete -P "$PREFIX" -D "resolve conflicts by discarding remote changes" mine-conflict complete -P "$PREFIX" -D "discard all local changes" theirs-full complete -P "$PREFIX" -D "resolve conflicts by discarding local changes" theirs-conflict complete -P "$PREFIX" -D "mark the current working copy as resolved" working case $SUBCMD in (resolve) ;; (*) complete -P "$PREFIX" -D "launch an editor to merge by hand" edit complete -P "$PREFIX" -D "launch a predefined external program" launch complete -P "$PREFIX" -D "leave the conflict unresolved" postpone ;; esac ;; #<<# # (c|--change) # ;; (--changelist|--cl) command -f completion/svn::completechangelist ;; (--config-dir) complete -P "$PREFIX" -S / -T -d ;; (--config-option) #TODO ;; (--depth|--set-depth) #>># complete -P "$PREFIX" -D "only the target itself" empty complete -P "$PREFIX" -D "the target and non-directory immediate children" files complete -P "$PREFIX" -D "the target and immediate children" immediates complete -P "$PREFIX" -D "the target and all of its descendants" infinity #<<# case $ARGOPT in (--set-depth) #>># complete -P "$PREFIX" -D "exclude the target from the parent" exclude esac #<<# ;; (--encoding) #TODO ;; (x|--extensions) WORDS=(diff) command -f completion//reexecute ;; (F|--file) complete -P "$PREFIX" -f ;; # (l|--limit) # ;; # (m|--message) # ;; # (--password) # ;; (--native-eol) #>># complete -P "$PREFIX" LF CR CRLF ;; #<<# (--new|--old) command -f completion/svn::completelocal -cm command -f completion/svn::completeurl ;; (r|--revision) typeset word="${TARGETWORD#"$PREFIX"}" PREFIX="${TARGETWORD%"${word#*:}"}" #>># complete -P "$PREFIX" -D "the latest revision in the repository" HEAD complete -P "$PREFIX" -D "the revision you checked out" BASE complete -P "$PREFIX" -D "the latest revision in which a change was made" COMMITTED complete -P "$PREFIX" -D "the revision immediately before COMMITTED" PREV ;; #<<# (--show-revs) #>># complete -P "$PREFIX" -D "info about merges already performed" merged complete -P "$PREFIX" -D "info about possible merges that can be performed" eligible ;; #<<# (--target) complete -P "$PREFIX" -f ;; (--username) complete -P "$PREFIX" -u ;; (--with-revprop) #TODO ;; (--*-cmd) WORDS=() command -f completion//reexecute -e ;; ('') if [ $separatorindex -eq ${WORDS[#]} ] || command -f completion/svn::containshelp; then command -f completion/svn::completesubcmd else if command -vf "completion/svn::$SUBCMD:arg" >/dev/null 2>&1; then command -f "completion/svn::$SUBCMD:arg" fi fi ;; esac } function completion/svn::containshelp { typeset opt for opt in "${WORDS[2,separatorindex-1]}"; do case $opt in (--help|-[h\?]) return 0 esac done return 1 } function completion/svn::setoptions { typeset opt i=1 for opt in "$ADDOPTIONS"; do if [ $# -le 0 ]; then break fi case " ${{opt%%;*}//:} " in (*" --$1 "*) OPTIONS=("$OPTIONS" "$opt") shift esac done } function completion/svn::completesubcmd { complete -P "$PREFIX" -D "add files for versioning" add complete -P "$PREFIX" -D "show files with author and revision info" blame praise annotate complete -P "$PREFIX" -D "print the contents of files" cat complete -P "$PREFIX" -D "put files into a changelist" changelist cl complete -P "$PREFIX" -D "check out a working copy from a repository" checkout co complete -P "$PREFIX" -D "resolve file locks and unfinished operations" cleanup complete -P "$PREFIX" -D "make a new revision in the repository" commit ci complete -P "$PREFIX" -D "copy a file" copy cp complete -P "$PREFIX" -D "delete files" delete remove rm complete -P "$PREFIX" -D "print differences between two files or revisions" diff complete -P "$PREFIX" -D "make a copy of the versioned directory tree" export complete -P "$PREFIX" -D "print usage of subcommands" help complete -P "$PREFIX" -D "commit new files into the repository" import complete -P "$PREFIX" -D "print info about versioned files" info complete -P "$PREFIX" -D "print a list of versioned files" list ls complete -P "$PREFIX" -D "lock files in the repository" lock complete -P "$PREFIX" -D "print commit log messages" log complete -P "$PREFIX" -D "apply changes between two files or revisions" merge complete -P "$PREFIX" -D "print info about merge" mergeinfo complete -P "$PREFIX" -D "make directories" mkdir complete -P "$PREFIX" -D "move files" move mv complete -P "$PREFIX" -D "apply a patch" patch complete -P "$PREFIX" -D "delete a property of files" propdel pdel complete -P "$PREFIX" -D "edit a property of files" propedit pedit complete -P "$PREFIX" -D "print a property of files" propget pget complete -P "$PREFIX" -D "print properties of files" proplist plist complete -P "$PREFIX" -D "set a property of files" propset pset complete -P "$PREFIX" -D "change the repository root URL" relocate complete -P "$PREFIX" -D "resolve conflicts" resolve # deprecated: complete -P "$PREFIX" -D "" resolved complete -P "$PREFIX" -D "undo local edits" revert complete -P "$PREFIX" -D "print the status of working copy" status complete -P "$PREFIX" -D "update working copy to a different URL" switch complete -P "$PREFIX" -D "unlock files in the repository" unlock complete -P "$PREFIX" -D "update the working copy" update complete -P "$PREFIX" -D "upgrade the working copy format" upgrade } function completion/svn::completeurl { typeset OPTIND=1 opt dironly=false while getopts d opt; do case $opt in (d) dironly=true;; esac done typeset target="${TARGETWORD#"$PREFIX"}" case $target in (?*://* | ^/*) ;; (*) return ;; esac typeset targetdir="$(dirname -- "$target"X)" typeset file prefix="${TARGETWORD%"${TARGETWORD##*/}"}" while read -r file; do case $file in (*/) complete -P "$prefix" -T -- "$file" ;; (*) if ! $dironly; then complete -P "$prefix" -- "$file" fi ;; esac done <(svn --non-interactive ls -- "${targetdir}/" 2>/dev/null) } function completion/svn::completelocal { typeset OPTIND=1 opt clean= modified= unversioned= while getopts cmu opt; do case $opt in (c) clean=true;; (m) modified=true;; (u) unversioned=true;; esac done complete -P "$PREFIX" -S / -T -d case "$clean-$modified-$unversioned" in (true-true-true) complete -P "$PREFIX" -f return ;; (--) return ;; esac typeset target="${TARGETWORD#"$PREFIX"}" typeset targetdir="$(dirname -- "$target"X)" if ! command -f completion/svn::isworkingdir "$targetdir"; then if [ "$unversioned" ]; then complete -P "$PREFIX" -f fi return fi typeset path prefix opt while read -r path; do if [ "$path" -ef "$targetdir" ]; then continue fi case $target in (*/*) prefix="$PREFIX${target%/*}/" ;; (*) prefix="$PREFIX" ;; esac complete -P "$prefix" -- "${path##*/}" done 2>/dev/null <( if [ "$unversioned" ]; then svn st --depth=files --no-ignore -- "$targetdir" | grep '^[?I]' | cut -c 9- fi if [ "$clean$modified" ]; then case $clean-$modified in (true-true) regex='^[^?I]';; (true- ) regex='^ ';; ( -true) regex='^[^ ?I]|^.[^ ]';; esac svn st -v --depth=files -- "$targetdir" | grep -E "$regex" | cut -c 10- | while read -r _ _ _ file; do printf '%s\n' "$file" done fi ) # XXX should honor the dotglob option } function completion/svn::completepropname { #>># complete -P "$PREFIX" -D "make the file executable" svn:executable complete -P "$PREFIX" -D "specify the file's MIME type" svn:mime-type complete -P "$PREFIX" -D "specify unversioned filenames not listed in status" svn:ignore complete -P "$PREFIX" -D "specify keywords substituted in file" svn:keywords complete -P "$PREFIX" -D "specify how end-of-line markers are manipulated" svn:eol-style complete -P "$PREFIX" -D "specify external paths and repositories" svn:externals complete -P "$PREFIX" -D "indicate that the file is not a regular file" svn:special complete -P "$PREFIX" -D "disable editing without lock" svn:needs-lock complete -P "$PREFIX" -D "info about merge" svn:mergeinfo } #<<# function completion/svn::completechangelist { typeset changelist while read -r changelist; do changelist=${changelist%\':} changelist=${changelist##*\'} complete -P "$PREFIX" "$changelist" done 2>/dev/null <(svn status | grep "^--- .*'.*':\$") } function completion/svn::isworkingdir ( typeset CDPATH= && cd -P -- "$1" 2>/dev/null && until [ -d .svn ]; do if [ . -ef .. ] || [ . -ef / ]; then return 1 fi cd -P .. done ) function completion/svn:::opt { command -f completion/svn::setoptions version } function completion/svn::add:opt { command -f completion/svn::setoptions \ auto-props depth force no-auto-props no-ignore parents quiet \ targets } function completion/svn::add:arg { command -f completion/svn::completelocal -u } function completion/svn::blame:opt { command -f completion/svn::setoptions \ extensions force incremental revision use-merge-history \ verbose xml } function completion/svn::blame:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::cat:opt { command -f completion/svn::setoptions revision } function completion/svn::cat:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::changelist:opt { command -f completion/svn::setoptions \ changelist depth quiet recursive remove targets } function completion/svn::changelist:arg { typeset i=2 remove=false while [ $i -lt $separatorindex ]; do case ${WORDS[i]} in (--remove) remove=true;; esac i=$((i+1)) done if ! $remove && [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then command -f completion/svn::completechangelist else command -f completion/svn::completelocal -cm fi } function completion/svn::checkout:opt { command -f completion/svn::setoptions \ depth force ignore-externals quiet revision } function completion/svn::checkout:arg { command -f completion/svn::completeurl if [ $(($separatorindex+1)) -lt ${WORDS[#]} ]; then complete -P "$PREFIX" -f fi } function completion/svn::cleanup:opt { command -f completion/svn::setoptions diff3-cmd } function completion/svn::cleanup:arg { complete -P "$PREFIX" -S / -T -d } function completion/svn::commit:opt { command -f completion/svn::setoptions \ changelist depth editor-cmd encoding file force-log \ kee-changelists message no-unlock quiet targets with-revprop } function completion/svn::commit:arg { command -f completion/svn::completelocal -m } function completion/svn::copy:opt { command -f completion/svn::setoptions \ editor-cmd encoding file force-log ignore-externals message \ parents quiet revision with-revprop } function completion/svn::copy:arg { command -f completion/svn::completelocal -cmu command -f completion/svn::completeurl } function completion/svn::delete:opt { command -f completion/svn::setoptions \ editor-cmd encoding file force force-log keep-local \ message quiet targets with-revprop } function completion/svn::delete:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::diff:opt { command -f completion/svn::setoptions \ change changelist depth diff-cmd extensions force git new \ no-diff-deleted notice-ancestry old revision \ show-copies-as-adds summarize xml } function completion/svn::diff:arg { # XXX should be relative to the argument of --old=... command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::export:opt { command -f completion/svn::setoptions \ depth force ignore-externals native-eol quiet revision } function completion/svn::export:arg { case $((${WORDS[#]} - $separatorindex)) in (1) command -f completion/svn::completelocal -cm command -f completion/svn::completeurl ;; (2) complete -P "$PREFIX" -f ;; esac } function completion/svn::help:opt { } function completion/svn::help:arg { command -f completion/svn::completesubcmd } function completion/svn::import:opt { command -f completion/svn::setoptions \ auto-props depth editor-cmd encoding file force force-log \ message no-auto-props no-ignore quiet with-revprop } function completion/svn::import:arg { case $((${WORDS[#]} - $separatorindex)) in (1) command -f completion/svn::completelocal -u ;; (2) command -f completion/svn::completeurl ;; esac } function completion/svn::info:opt { command -f completion/svn::setoptions \ changelist depth incremental recursive revision targets xml } function completion/svn::info:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::list:opt { command -f completion/svn::setoptions \ depth incremental recursive revision verbose xml } function completion/svn::list:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::lock:opt { command -f completion/svn::setoptions \ encoding file force force-log message targets } function completion/svn::lock:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::log:opt { command -f completion/svn::setoptions \ change incremental limit quiet revision stop-on-copy targets \ use-merge-history verbose with-all-revprops with-no-revprops \ with-revprop xml } function completion/svn::log:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::merge:opt { command -f completion/svn::setoptions \ accept allow-mixed-revisions change depth diff3-cmd dry-run \ extensions force ignore-ancestry quiet record-only reintegrate \ revision } function completion/svn::merge:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::mergeinfo:opt { command -f completion/svn::setoptions revision show-revs } function completion/svn::mergeinfo:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::mkdir:opt { command -f completion/svn::setoptions editor-cmd encoding file \ force-log message parents quiet with-revprop } function completion/svn::mkdir:arg { complete -P "$PREFIX" -S / -T -d command -f completion/svn::completeurl -d } function completion/svn::move:opt { command -f completion/svn::setoptions editor-cmd encoding file force \ force-log message parents quiet revision with-revprop } function completion/svn::move:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::patch:opt { command -f completion/svn::setoptions \ dry-run ignore-whitespace quiet reverse-diff strip } function completion/svn::patch:arg { if [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then complete -P "$PREFIX" -f else complete -P "$PREFIX" -S / -T -d fi } function completion/svn::propdel:opt { command -f completion/svn::setoptions \ changelist depth quiet recursive revision revprop } function completion/svn::propdel:arg { if [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then command -f completion/svn::completepropname else command -f completion/svn::completelocal -cm command -f completion/svn::completeurl fi } function completion/svn::propedit:opt { command -f completion/svn::setoptions editor-cmd encoding file force \ force-log message revision revprop with-revprop } function completion/svn::propedit:arg { if [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then command -f completion/svn::completepropname else command -f completion/svn::completelocal -cm command -f completion/svn::completeurl fi } function completion/svn::propget:opt { command -f completion/svn::setoptions \ changelist depth recursive revision revprop strict verbose xml } function completion/svn::propget:arg { if [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then command -f completion/svn::completepropname else command -f completion/svn::completelocal -cm command -f completion/svn::completeurl fi } function completion/svn::proplist:opt { command -f completion/svn::setoptions \ changelist depth quiet recursive revision revprop verbose xml } function completion/svn::proplist:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::propset:opt { command -f completion/svn::setoptions changelist depth encoding file \ force quiet recursive revision revprop targets } function completion/svn::propset:arg { typeset i=2 hasfile=false while [ $i -lt $separatorindex ]; do case ${WORDS[i]} in (--file=*|-F*) hasfile=true;; esac i=$((i+1)) done if [ $(($separatorindex+1)) -eq ${WORDS[#]} ]; then command -f completion/svn::completepropname elif ! $hasfile && [ $(($separatorindex+2)) -eq ${WORDS[#]} ]; then # there is no completion for a property value else command -f completion/svn::completelocal -cm command -f completion/svn::completeurl fi } function completion/svn::relocate:opt { command -f completion/svn::setoptions ignore-externals } function completion/svn::relocate:arg { command -f completion/svn::switch:arg "$@" } function completion/svn::resolve:opt { command -f completion/svn::setoptions \ accept depth quiet recursive targets } function completion/svn::resolve:arg { command -f completion/svn::completelocal -cm } function completion/svn::resolved:opt { command -f completion/svn::setoptions \ depth quiet recursive targets } function completion/svn::resolved:arg { command -f completion/svn::completelocal -cm } function completion/svn::revert:opt { command -f completion/svn::setoptions \ changelist depth quiet recursive targets } function completion/svn::revert:arg { command -f completion/svn::completelocal -m } function completion/svn::status:opt { command -f completion/svn::setoptions \ changelist depth ignore-externals incremental no-ignore quiet \ show-updates verbose xml } function completion/svn::status:arg { command -f completion/svn::completelocal -cm } function completion/svn::switch:opt { command -f completion/svn::setoptions \ accept depth diff3-cmd force ignore-externals ignore-ancestry \ quiet relocate revision set-depth } function completion/svn::switch:arg { if [ $(($separatorindex+1)) -lt ${WORDS[#]} ]; then complete -P "$PREFIX" -S / -T -d fi command -f completion/svn::completeurl } function completion/svn::unlock:opt { command -f completion/svn::setoptions force targets } function completion/svn::unlock:arg { command -f completion/svn::completelocal -cm command -f completion/svn::completeurl } function completion/svn::update:opt { command -f completion/svn::setoptions \ accept changelist depth diff3-cmd editor-cmd force \ ignore-externals parents quiet revision set-depth } function completion/svn::update:arg { command -f completion/svn::completelocal -cm } function completion/svn::upgrade:opt { command -f completion/svn::setoptions quiet } function completion/svn::upgrade:arg { complete -P "$PREFIX" -S / -T -d } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/tail000066400000000000000000000045661354143602500165610ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "tail" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/tail { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c: ${long:+--bytes:}; specify the number of bytes to print" "f; keep watching the file for more data" "n: ${long:+--lines:}; specify the number of lines to print" ) #<# case $type in (*BSD|Darwin|SunOS) OPTIONS=("$OPTIONS" #># "r; print lines in reverse order" ) #<# esac case $type in (*BSD|Darwin|HP-UX) OPTIONS=("$OPTIONS" #># "b:; specify the number of 512-byte blocks to print" ) #<# esac case $type in (FreeBSD|NetBSD|Darwin) OPTIONS=("$OPTIONS" #># "F; like -f, but reopen the file if renamed" ) #<# esac case $type in (GNU|FreeBSD) OPTIONS=("$OPTIONS" #># "q ${long:+--quiet --silent}; never print filename headers" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--follow::; specify how to watch the file for more data" "F; like --follow=name --retry" "--max-unchanged-stats:; recheck file status every n checks" "--pid:; specify the ID of a process to follow" "--retry; try to reopen the file if removed" "--sleep-interval:; specify the number of seconds to wait between checks for more data" "v --verbose; always print filename headers" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([bcn]|--bytes|--lines) case $type in (GNU) if command -vf completion//prefixdigits >/dev/null 2>&1 || . -AL completion/_blocksize; then if command -f completion//prefixdigits; then command -f completion//completesizesuffix b GNU fi fi esac ;; (--follow) #>># complete -P "$PREFIX" -D "never reopen the file" descriptor complete -P "$PREFIX" -D "reopen the file when renamed/removed" name ;; #<<# (--pid) typeset pid args while read -r pid args; do complete -P "$PREFIX" -D "$args" -- "$pid" done <(ps -A -o pid= -o args= 2>/dev/null) ;; ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/tar000066400000000000000000000503231354143602500164060ustar00rootroot00000000000000# (C) 2011-2012 magicant # Completion script for the "tar" command. # Supports SUSv2, GNU tar 1.25, BSD tar 2.8, OpenBSD 4.8, NetBSD 5.1, # SunOS 5.11, HP-UX 11i v3. function completion/tar { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU '*) typeset type=GNU ;; (*'bsdtar'*) typeset type=BSD ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac typeset long= gnubsd= gnu= bsd= openbsd= netbsd= sunos= hpux= case $type in (GNU) long=true gnubsd=true gnu=true esac case $type in (BSD) long=true gnubsd=true bsd=true esac case $type in (OpenBSD) openbsd=true esac case $type in (NetBSD) long=true netbsd=true esac case $type in (SunOS) sunos=true esac case $type in (HP-UX) hpux=true esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "${sunos:+@}; include extended attributes in the archive" "${sunos:+/}; include extended system attributes in the archive" "${gnu:+? }${gnubsd:+--help}; print help" "${gnu:+A --catenate --concatenate}; append archives to another archive" "${sunos:+A}; suppress warnings about ACL" "${gnu:+a --auto-compress}; compress the archive according to the filename suffix" "${gnu:+B --read-full-records}${bsd:+B --read-full-blocks}${netbsd:+B --read-full-blocks}${sunos:+B}; keep reading until data are fully read" "b: ${gnu:+--blocking-factor:}${bsd:+--block-size:}${netbsd:+--blocking-factor:}; specify the blocking factor" "${long:+C: --directory: ${bsd:+--cd:}}${openbsd:+C:}${sunos:+C:}; specify a directory to operate in" "c ${long:+--create}; create a new archive" "${sunos:+D}; warn when a file is modified while being read by tar" "${gnu:+d --diff --compare}; compare archive contents to files in the file system" "${sunos:+E}; write extended headers" "${openbsd:+e}${netbsd:+e}${sunos:+e}; abort on any error" "${hpux:+e}; fail if the extent attributes are present in the files archived" "${gnu:+F: --info-script: --new-volume-script:}; specify a script run at the end of each tape" "${sunos:+F}; exclude directories named SCCS and RCS" "f: ${long:+--file:}; specify the archive file to operate on" "${gnu:+G --incremental}; do old GNU-style incremental backup" "${gnu:+g: --listed-incremental:}; specify a snapshot file to do new GNU-style incremental backup with" "${gnu:+H: --format:}${bsd:+--format:}; specify the archive format" "${bsd:+H}${openbsd:+H}${netbsd:+H}; follow symbolic links in operands" "${long:+h --dereference ${bsd:+L}}${openbsd:+h L}${sunos:+h}${hpux:+h}; follow all symbolic links" "${long:+${gnu:+I: }--use-compress-program:}; specify a program to (de)compress the archive" "${sunos:+i}; ignore directory checksum errors" "${gnubsd:+J --xz}; use xz to (de)compress the archive" "${long:+j ${bsd:+y} --bzip2 ${netbsd:+--bunzip2}}${openbsd:+j}${sunos:+j}; use bzip2 to (de)compress the archive" "${gnu:+K: --starting-file:}; specify a file in the archive to start extraction from" "${long:+k --keep-old-files}; don't overwrite existing files in extraction" "${gnu:+L: --tape-length:}; specify the tape length" "${gnu:+M --multi-volume}; operate on a multi-volume archive" "m ${gnu:+--touch}${bsd:+--modification-time}${netbsd:+--modification-time}; don't restore modification times when extracting" "${gnu:+N: --newer: --after-date:}; only add files newer than the specified date or file" "${openbsd:+N}${gnubsd:+--numeric-owner}; use numeric user/group IDs only" "${hpux:+N}; create a POSIX format archive" "${gnu:+n --seek}${sunos:+n}; assume the archive is random-accessible" "${bsd:+n}${long:+ --no-recursion}${bsd:+ --norecurse}; don't recursively operate on directories" "${gnu:+P --absolute-names}${bsd:+P --absolute-paths --insecure}${openbsd:+P}${netbsd:+P --absolute-paths}${sunos:+P}; preserve absolute pathnames in the archive" "${gnu:+p --preserve-permissions --same-permissions}; preserve file permissions" "${bsd:+p --preserve-permissions --same-permissions}${openbsd:+p}${sunos:+p}${hpux:+p}; preserve file ownerships and permissions" "${bsd:+q --fast-read}${openbsd:+q}${netbsd:+q --fast-read}; extract only the first matching file in the archive" "${gnu:+R --block-number}; include block numbers in error messages" "r ${long:+--append}; append files to an existing archive" "${gnu:+S --sparse}${bsd:+S}${netbsd:+S --sparse}; treat sparse files efficiently" "${gnu:+s --preserve-order --same-order}; assume file operands are sorted according to the archive contents" "${gnu:+--transform: --xform:}${bsd:+s:}${openbsd:+s:}${netbsd:+s:}; specify a regular expression to modify filenames" "${long:+T: --files-from: ${bsd:+I:}}${openbsd:+I:}; specify a file containing the names of files to archive/extract" "${sunos:+T}; store/check sensitivity label of files in the archive" "t ${long:+--list}; list files in an existing archive" "${gnubsd:+U --unlink-first}; remove existing files before extraction" "u ${long:+--update}; update files in an archive" "${gnu:+V: --label:}; specify a file label" "${hpux:+V}; print file types (only when listing)" "v ${gnubsd:+--verbose}; print files being processed" "${gnu:+W --verify}; verify the archive was correctly written" "${bsd:+W:}" "w ${long:+--interactive --confirmation}; confirm before processing each file" "${long:+X: --exclude-from:}${sunos:+X:}; specify a file containing the names of files not to be archived" "${openbsd:+X}${gnubsd:+--one-file-system}${netbsd:+l --one-file-system}; archive files only in the same file system" "${long:+Z --compress --uncompress}${openbsd:+Z}${sunos:+Z}; use compress to (de)compress the archive" "${long:+z --gzip --gunzip ${gnu:+--ungzip}}${openbsd:+z}${sunos:+z}; use gzip to (de)compress the archive" "x ${long:+--extract ${gnu:+--get}${netbsd:+--get}}; extract files from an existing archive" "${gnu:+--anchored}; force patterns to match only the beginning of filename components" "${gnu:+--atime-preserve::}${netbsd:+--atime-preserve}; read files without updating access time" "${gnu:+--backup:}; specify how to make a backup before overwriting files" "${gnu:+--check-device}; check device numbers in incremental archiving" "${gnu:+--checkpoint::}; print periodic checkpoint messages" "${gnu:+--checkpoint-action:}; specify the action to take on each checkpoint" "${bsd:+--chroot}${netbsd:+--chroot}; chroot to the current directory before extraction" "${gnu:+--delay-directory-restore}; delay restoring directory permissions until all files are extracted" "${gnu:+--delete}; delete files from an archive" "${gnubsd:+--exclude:}; skip files whose names match the specified pattern" "${gnu:+--exclude-backups}; exclude backup and lock files" "${gnu:+--exclude-caches}; exclude cache directory contents except the tag files" "${gnu:+--exclude-caches-under}; exclude cache directory contents and tag files" "${gnu:+--exclude-caches-all}; exclude cache directories" "${gnu:+--exclude-tag:}; exclude contents of directories containing the specified file except the file" "${gnu:+--exclude-tag-under:}; exclude contents of directories containing the specified file" "${gnu:+--exclude-tag-all:}; exclude directories containing the specified file" "${gnu:+--exclude-vcs}; exclude internal files used by version control systems" "${gnu:+--force-local}; always treat filenames as local pathnames" "${gnu:+--full-time}; show timestamps in full resolution" "${gnu:+--group:}; specify the group ID of archived files" "${gnu:+--hard-dereference}; treat hard links to the same file separately" "${gnu:+--ignore-case}; case-insensitive pattern matching" "${gnu:+--ignore-command-error}; ignore errors in subprocesses" "${gnu:+--ignore-failed-read}; ignore unreadable files" "${gnu:+--ignore-zeros}; ignore zeroed blocks in the archive" "${bsd:+--include:}; archive only files whose names match the specified pattern" "${gnu:+--index-file:}; specify a file where verbose output go" "${netbsd:+--insecure}; accept pathnames containing .." "${gnubsd:+--keep-newer-files}; don't overwrite existing files newer than archive contents" "${gnu:+--level:}; specify the level of incremental backup" "${gnu:+--lzip}; use lzip to (de)compress the archive" "${gnubsd:+--lzma}; use lzma to (de)compress the archive" "${gnu:+--lzop}; use lzop to (de)compress the archive" "${gnu:+--mode:}; specify the permission of archived files" "${gnu:+--mtime:}; specify the modification time of archived files" "${bsd:+--newer-ctime:}; only add files newer than the specified date" "${bsd:+--newer-ctime-than: --newer-than:}; only add files newer than the specified file" "${bsd:+--newer-mtime:}; only add files newer than the specified date" "${bsd:+--newer-mtime-than:}; only add files newer than the specified file" "${gnu:+--no-anchored}; allow patterns to match any part of filename components" "${gnu:+--no-auto-compress}; don't automatically compress the archive according to the filename suffix" "${gnu:+--no-check-device}; don't check device numbers in incremental archiving" "${gnu:+--no-delay-directory-restore}; cancel the --delay-directory-restore option" "${bsd:+--nodump}; skip files with the nodump flag" "${gnu:+--no-ignore-case}; case-sensitive pattern matching" "${gnu:+--no-ignore-command-error}; warn about errors in subprocesses" "${gnu:+--no-null}; cancel the --null option" "${gnu:+--no-overwrite-dir}; preserve metadata of existing directories when extracting" "${gnu:+--no-quote-chars:}; specify characters to be removed from the list of quoted characters" "${gnubsd:+--no-same-permissions}; don't restore file permissions when extracting" "${gnu:+--no-seek}; assume the archive media doesn't support random access" "${gnu:+--no-unquote}; don't interpret escape characters" "${gnu:+--no-wildcards}; don't use wildcards" "${gnu:+--no-wildcards-match-slash}; wildcards don't match slashes" "${gnubsd:+--null}; use null-separated list of pathnames" "${gnubsd:+--numeric-owner}; use numeric user/group IDs" "${gnu:+--occurrence::}; process the nth occurrence of each file in the archive" "${bsd:+--options:}; specify options for internal modules" "${gnu:+--overwrite}; overwrite existing files when extracting" "${gnu:+--overwrite-dir}; overwrite directory metadata when extracting" "${gnu:+--owner:}; specify the owner of files in the created archive" "${gnu:+--pax-option:}; create a pax archive with the specified options" "${gnu:+--posix}; like --format=posix" "${gnu:+--preserve}; like --preserve-permissions --preserve-order" "${gnu:+--quote-chars:}; specify characters to always quote" "${gnu:+--quoting-style:}; specify how to quote filenames" "${gnu:+--record-size:}; specify the record size" "${gnu:+--recursion}; recursively operate on directories" "${gnu:+--recursive-unlink}; remove entire existing directories before extracting directories" "${gnu:+--remove-files}; remove original files after archiving them" "${gnu:+--restrict}; disallow some potentially harmful options" "${gnu:+--rmt-command:}; specify the rmt command" "${gnu:+--rsh-command:}; specify the rsh command" "${gnu:+--same-owner}; preserve file owners when extracting" "${gnu:+--show-defaults}; print the default option settings" "${gnu:+--show-omitted-dirs}; print unprocessed directories" "${gnu:+--show-transformed-names --show-stored-names}; print names of processed files in the archive" "${gnu:+--sparse-version:}; specify the format version used for sparse files" "${netbsd:+--strict}; disable GNU extensions" "${gnubsd:+--strip-components:}; specify the number of leading pathname components to remove in extraction" "${gnu:+--suffix:}; specify a suffix to append to backup file names" "${gnu:+--test-label}; only print the volume label" "${gnu:+--to-command:}; specify a command that receives extracted file contents" "${gnubsd:+--totals${gnu:+::}}; print total byte count" "${gnu:+--unquote}; unquote input pathnames" "${gnu:+--utc}; print dates in Coordinated Universal Time (UTC)" "${gnubsd:+--version}; print version info" "${gnu:+--volno-file:}; specify a file to keep track of volumes with" "${gnu:+--warning:}; specify a warning category to enable or disable" "${gnu:+--wildcards}; enable wildcards" "${gnu:+--wildcards-match-slash}; wildcards match slashes" ) #<# case $type in (OpenBSD|NetBSD) OPTIONS=("$OPTIONS" #># "0; use /dev/rst0" "1; use /dev/rst1" "4; use /dev/rst4" "5; use /dev/rst5" "7; use /dev/rst7" "8; use /dev/rst8" ) #<# ;; (SunOS) OPTIONS=("$OPTIONS" #># "0; use the default tape drive" "1; use alternate tape drive 1" "2; use alternate tape drive 2" "3; use alternate tape drive 3" "4; use alternate tape drive 4" "5; use alternate tape drive 5" "6; use alternate tape drive 6" "7; use alternate tape drive 7" ) #<# ;; esac case $type in (NetBSD) ;; (*) OPTIONS=("$OPTIONS" #># "l ${gnu:+--check-links}${bsd:+--check-links}; warn unless all hard links to each file are archived" ) #<# ;; esac # parse old-style options typeset SAVEWORDS SAVEWORDS=("$WORDS") set -- "${WORDS[2,-1]}" if [ $# -eq 0 ]; then set -- "$TARGETWORD" fi case $1 in (-*) typeset oldopt=false ;; (*) typeset oldopt="$1" oldopt1 opt WORDS=("${WORDS[1]}") shift while [ "$oldopt" ]; do oldopt1=${oldopt[1]} for opt in ${OPTIONS%%;*}; do case $opt in (${oldopt1}) WORDS=("$WORDS" -$oldopt1) ;; (${oldopt1}:) if [ $# -gt 0 ]; then WORDS=("$WORDS" -$oldopt1 "$1") shift else WORDS=("$WORDS" -$oldopt1) break 2 fi ;; esac done oldopt=${oldopt#?} done WORDS=("$WORDS" "$@") oldopt=true ;; esac command -f completion//parseoptions -es # add other options typeset word file= compressopts in=true compressopts=() for word in "$WORDS"; do case $word in (-[cru]|--append|--create|--update) OPTIONS=("$OPTIONS" #># "${openbsd:+O}${netbsd:+O}${hpux:+O}; use old non-POSIX archive format" "${long:+o${gnu:+ --old-archive}${netbsd:+ --old-archive --portability}}${openbsd:+o}${hpux:+o}; use V7-compatible archive format" ) #<# ;; (--delete) in=false ;; (-t|--list) in=false OPTIONS=("$OPTIONS" #># "${bsd:+O --to-stdout}; print file list to the standard error" ) #<# ;; (-x|--extract|--get) in=false OPTIONS=("$OPTIONS" #># "${long:+O ${gnubsd:+--to-stdout}}; extract files to the standard output" "o ${gnubsd:+--no-same-owner}; don't restore owners of extracted files" ) #<# ;; (-f*) file=${word#-f} ;; (--file=*) file=${word#--file=} ;; (-[aJjyZz]|--auto-compress|--bunzip2|--bzip2|--compress|--gunzip|--gzip|--lzip|--lzma|--lzop|--no-auto-compress|--uncompress|--ungzip|--use-compress-program=*|--xz) compressopts=("$compressopts" "$word") ;; (-I*) if [ "$gnu" ]; then compressopts=("$compressopts" "$word") fi ;; (--) break ;; esac done if $oldopt && [ ${SAVEWORDS[#]} -le 1 ]; then command -f completion//completeoptions -fs else case $ARGOPT in (-) command -f completion//completeoptions ;; (b|--blocking-factor|--block-size|--record-size) ;; (C|--cd|--directory) complete -P "$PREFIX" -S / -T -d ;; # (F|--info-script|--new-volume-script) # complete -P "$PREFIX" -f # ;; (f|--file) complete -P "$PREFIX" -S / -T -d { typeset generated=false ext for ext in .tar .tar.gz .tar.bz2 .tar.z .tar.Z .tar.lzma .tar.xz .tgz .tbz .taz .tlz .txz; do if complete -P "$PREFIX" -A "*$ext" -f; then generated=true fi done $generated } || complete -P "$PREFIX" -f ;; # (g|--listed-incremental) # complete -P "$PREFIX" -f # ;; (H|--format) #>># complete -P "$PREFIX" -D "Unix V7" v7 complete -P "$PREFIX" -D "GNU tar 1.12 or earlier" oldgnu complete -P "$PREFIX" -D "GNU tar 1.13" gnu complete -P "$PREFIX" -D "POSIX.1-1988" ustar complete -P "$PREFIX" -D "POSIX.1-2001" posix ;; #<<# (I|--use-compress-program) WORDS=() command -f completion//reexecute -e ;; # (K|--starting-file) # same as ('') # ;; # (L|--tape-length) # ;; # (N|--newer|--after-date) # complete -P "$PREFIX" -f # ;; # ([IT]|--files-from) # complete -P "$PREFIX" -f # ;; # (V|--label) # complete -P "$PREFIX" -f # ;; # (W) # # not supported # ;; # (X|--exclude-from) # complete -P "$PREFIX" -f # ;; (--atime-preserve) #>># complete -P "$PREFIX" -D "remember timestamp and restore it after reading file" replace complete -P "$PREFIX" -D "use a special reading method to keep timestamp" system ;; #<<# (--backup) if command -vf completion//completebackup >/dev/null 2>&1 || . -AL completion/_backup; then command -f completion//completebackup fi ;; (--checkpoint) ;; (--checkpoint-action) case ${TARGETWORD#"$PREFIX"} in (exec=*) PREFIX=${PREFIX}exec= WORDS=() command -f completion//reexecute -e ;; (*) #>># complete -P "$PREFIX" -D "ring a bell on the terminal" bell complete -P "$PREFIX" -D "print a dot" dot . complete -P "$PREFIX" -D "print the specified text to the standard error" -S = -T echo complete -P "$PREFIX" -D "execute the specified command" -S = -T exec complete -P "$PREFIX" -D "wait for the specified seconds" -S = -T sleep complete -P "$PREFIX" -D "print the specified text to the terminal" -S = -T ttyout ;; #<<# esac ;; # (--exclude) # complete -P "$PREFIX" -f # ;; # (--exclude-tag*) # complete -P "$PREFIX" -f # ;; (--group) complete -P "$PREFIX" -g ;; # (--include) # complete -P "$PREFIX" -f # ;; # (--index-file) # complete -P "$PREFIX" -f # ;; (--level) complete -P "$PREFIX" 0 ;; (--mode) if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode tar fi ;; # (--mtime) # ;; # (--newer-[cm]time) # ;; # (--newer-[cm]time-than|--newer-than) # complete -P "$PREFIX" -f # ;; (--occurrence) ;; (--options) ;; (--owner) complete -P "$PREFIX" -u ;; (--pax-option) # TODO: not yet supported # This option is equivalent to pax's -o option ;; (--quote-chars|--no-quote-chars) ;; (--rmt-command|--rsh-command) WORDS=() command -f completion//reexecute -e ;; (--sparse-vertion) #>># # TODO: description complete -P "$PREFIX" 0.0 complete -P "$PREFIX" 0.1 complete -P "$PREFIX" 1.0 ;; #<<# (--strip-components) ;; (--to-command) WORDS=() command -f completion//reexecute -e ;; (--totals) case ${TARGETWORD#"$PREFIX"} in (SIG*) PREFIX=${PREFIX}SIG esac complete -P "$PREFIX" -A HUP --signal complete -P "$PREFIX" -A QUIT --signal complete -P "$PREFIX" -A INT --signal complete -P "$PREFIX" -A USR1 --signal complete -P "$PREFIX" -A USR2 --signal ;; # (--volno-file) # complete -P "$PREFIX" -f # ;; (--warning) #>># complete -P "$PREFIX" -D "enable all warning messages" all complete -P "$PREFIX" -D "disable all warning messages" none # TODO: description complete -P "$PREFIX" alone-zero-block complete -P "$PREFIX" bad-dumpdir complete -P "$PREFIX" cachedir complete -P "$PREFIX" contiguous-cast complete -P "$PREFIX" decompress-program complete -P "$PREFIX" file-changed complete -P "$PREFIX" file-ignored complete -P "$PREFIX" file-removed complete -P "$PREFIX" file-shrank complete -P "$PREFIX" file-unchanged complete -P "$PREFIX" filename-with-nuls complete -P "$PREFIX" ignore-archive complete -P "$PREFIX" ignore-newer complete -P "$PREFIX" new-directory complete -P "$PREFIX" rename-directory complete -P "$PREFIX" symlink-cast complete -P "$PREFIX" timestamp complete -P "$PREFIX" unknown-cast complete -P "$PREFIX" unknown-keyword complete -P "$PREFIX" xdev ;; #<<# (''|K|--starting-file) if $in; then complete -P "$PREFIX" -f else command -f completion/tar::completecontainedfiles fi ;; (*) complete -P "$PREFIX" -f ;; esac fi } function completion/tar::completecontainedfiles { if ! [ "$file" ]; then return fi typeset targetpath="${TARGETWORD#"$PREFIX"}" typeset targetfile="${targetpath##*/}" typeset targetdir="${targetpath%"$targetfile"}" typeset path opt while read -r path; do path="${path#"$targetdir"}" opt= if [ "$path" ]; then case $path in (*/*) path=${path%%/*} opt='-S / -T' esac complete -P "$PREFIX$targetdir" $opt -- "$path" fi done 2>/dev/null \ <("${SAVEWORDS[1]}" -t -f "$file" "$compressopts" -- \ ${targetdir:+"$targetdir"} /dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a ${long:+--append}; append to the output file; don't overwrite the file" "i ${long:+--ignore-interrupts}; ignore the SIGINT signal" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/test000066400000000000000000000075241354143602500166040ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "test" built-in command. # Completion function "completion/test" is used for the "[" built-in as well. function completion/test { WORDS=('(' "${WORDS[2,-1]}") # complete unary operator case ${WORDS[-1]} in ('('|!|-[ao]) #>># complete -D "check if a file is a block special file" -- -b complete -D "check if a file is a character special file" -- -c complete -D "check if a file is a directory" -- -d complete -D "check if a file exists" -- -e complete -D "check if a file is a regular file" -- -f complete -D "check if a file is owned by the current group" -- -G complete -D "check if a file's set-group-ID flag is set" -- -g complete -D "check if a file is a symbolic link" -- -h complete -D "check if a file's sticky bit is set" -- -k complete -D "check if a file is a symbolic link" -- -L complete -D "check if a file hasn't been accessed since last modified" -- -N complete -D "check if a file is owned by the current user" -- -O complete -D "check if a shell option is enabled" -- -o complete -D "check if a file is a FIFO (named pipe)" -- -p complete -D "check if a file is readable" -- -r complete -D "check if a file is a socket" -- -S complete -D "check if a file is not empty" -- -s complete -D "check if a file's set-user-ID flag is set" -- -u complete -D "check if a file is writable" -- -w complete -D "check if a file is executable" -- -x complete -D "check if a file descriptor is a terminal" -- -t complete -D "check if a string is not empty" -- -n complete -D "check if a string is empty" -- -z esac #<<# # complete binary operator case ${WORDS[-2]} in ('('|!|-[ao]) #>># complete -D "check if a file is newer than another" -- -nt complete -D "check if a file is older than another" -- -ot complete -D "check if a file is a hard link to another" -- -ef complete -D "check if a string is the same as another" -- = complete -D "check if a string is the same as another" -- == complete -D "check if a string is matched by a regex" -- =~ complete -D "check if a string is different from another" -- != complete -D "check if a string is the same as another according to the current locale" -- === complete -D "check if a string is different from another according to the current locale" -- !== complete -D "compare two strings in the alphabetical order" -- '<' '<=' '>' '>=' complete -D "check if an integer is equal to another" -- -eq complete -D "check if an integer is unequal to another" -- -ne complete -D "check if an integer is greater than another" -- -gt complete -D "check if an integer is greater than or equal to another" -- -ge complete -D "check if an integer is less than another" -- -lt complete -D "check if an integer is less than or equal to another" -- -le complete -D "check if a version number is equal to another" -- -veq complete -D "check if a version number is unequal to another" -- -vne complete -D "check if a version number is greater than another" -- -vgt complete -D "check if a version number is greater than or equal to another" -- -vge complete -D "check if a version number is less than another" -- -vlt complete -D "check if a version number is less than or equal to another" -- -vle esac #<<# # complete operand of the "-o" unary operator case ${WORDS[-2]} in ('('|!|-[ao]) case ${WORDS[-1]} in (-o) if command -fv completion/set::completelongoption >/dev/null 2>&1 || . -AL completion/set; then typeset SOPTIONS LOPTIONS PREFIX prog=set SOPTIONS=() LOPTIONS=() case $TARGETWORD in (\?*) TARGETWORD=${TARGETWORD#\?} PREFIX=\? ;; (*) PREFIX= ;; esac command -f completion/set::getopt set command -f completion/set::completelongoption fi return esac esac # complete operand of other operators complete -f } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/tig000066400000000000000000000010411354143602500163740ustar00rootroot00000000000000# (C) 2014 magicant # Completion script for the "tig" command. # Supports Git 1.7.7. function completion/tig { if [ "${WORDS[#]}" -eq 1 ]; then complete -D "show a file with commit info" blame complete -D "search files using regular expressions" grep complete -D "show commit logs" log complete -D "show objects" show complete -D "manipulate stashes" stash complete -D "show the status of the working tree" status fi WORDS=(git log "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/time000066400000000000000000000062661354143602500165650ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "time" command. # Supports POSIX 2008, GNU time 1.7, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/time { case $("${WORDS[1]}" --version 2>&1) in (*'GNU'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "p ${long:+--portability}; use the POSIX format" ) #<# case $type in (GNU|FreeBSD) OPTIONS=("$OPTIONS" #># "a ${long:+--append}; append to the output file; don't overwrite the file" "o: ${long:+--output:}; specify the file to print time to" ) #<# esac case $type in (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "l; print the rusage values as well" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "f: --format:; specify the output format" "V --version; print version info" "v --verbose; print detailed time info" "--help" ) #<# ;; (FreeBSD) OPTIONS=("$OPTIONS" #># "h; use a human-friendly format" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "c; use the csh format" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (f|--format) command -f completion/time::format ;; (o|--output) complete -P "$PREFIX" -f ;; (*) command -f completion//getoperands command -f completion//reexecute -e ;; esac } function completion/time::format { typeset word="${TARGETWORD#"$PREFIX"}" word=${word//%%} case $word in (*%) PREFIX=${TARGETWORD%\%} #>># complete -T -P "$PREFIX" -D "command name and arguments" '%C' complete -T -P "$PREFIX" -D "preemptive context switch count" '%c' complete -T -P "$PREFIX" -D "average unshared data size in kilobytes" '%D' complete -T -P "$PREFIX" -D "elapsed real time ([H:]MM:SS.ss)" '%E' complete -T -P "$PREFIX" -D "elapsed real time in seconds" '%e' complete -T -P "$PREFIX" -D "major fault count" '%F' complete -T -P "$PREFIX" -D "file read count" '%I' complete -T -P "$PREFIX" -D "average total used memory in kilobytes" '%K' complete -T -P "$PREFIX" -D "number of signals the process received" '%k' complete -T -P "$PREFIX" -D "max resident set size in kilobytes" '%M' complete -T -P "$PREFIX" -D "file write count" '%O' complete -T -P "$PREFIX" -D "percentage of CPU time" '%P' complete -T -P "$PREFIX" -D "average unshared stack size in kilobytes" '%p' complete -T -P "$PREFIX" -D "minor fault count" '%R' complete -T -P "$PREFIX" -D "socket receive count" '%r' complete -T -P "$PREFIX" -D "kernel CPU time in seconds" '%S' complete -T -P "$PREFIX" -D "socket send count" '%s' complete -T -P "$PREFIX" -D "average resident set size in kilobytes" '%t' complete -T -P "$PREFIX" -D "swap-out count" '%W' complete -T -P "$PREFIX" -D "non-preemptive context switch count" '%w' complete -T -P "$PREFIX" -D "average shared text area size in kilobytes" '%X' complete -T -P "$PREFIX" -D "exit status of the process" '%x' complete -T -P "$PREFIX" -D "page size" '%Z' complete -T -P "$PREFIX" -D "%" '%%' return 0 #<<# esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/touch000066400000000000000000000034321354143602500167410ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "touch" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/touch { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a; change the last access time only" "c ${long:+--no-create}; don't create files if missing; ignore missing files" "d: ${long:+--date:}; specify the time to change to (new syntax)" "m; change the last modified time only" "r: ${long:+--reference:}; change to the time of the specified file" "t:; specify the time to change to (traditional syntax)" ) #<# case $type in (FreeBSD) OPTIONS=("$OPTIONS" #># "A:; specify the time to change to (don't change date)" ) #<# esac case $type in (Darwin|FreeBSD|NetBSD) OPTIONS=("$OPTIONS" #># "f; try to change times regardless of permissions" ) #<# esac case $type in (GNU|FreeBSD|NetBSD) OPTIONS=("$OPTIONS" #># "h ${long:+--no-dereference}; change the time of a symbolic link itself" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--time:; specify the type of time to change" "--help" "--version" ) #<# esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (A) ;; (d|--date) ;; (t) ;; (--time) #>># complete -P "$PREFIX" -D "change the last access time only" atime access use complete -P "$PREFIX" -D "change the last modified time only" mtime modify ;; #<<# (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/tr000066400000000000000000000046201354143602500162440ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "tr" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/tr { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "C; complement the set of characters in the first operand" "c ${long:+--complement}; complement the set of bytes in the first operand" "d ${long:+--delete}; delete characters in the first operand" "s ${long:+--squeeze-repeats}; squeeze repeated characters in the last operand into one" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "t --truncate-set1; truncate the first operand to the length of the last operand" "--help" "--version" ) #<# ;; (FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "u; disable output buffering" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "A; don't handle multibyte characters" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (*) command -f completion/tr::class || if command -vf completion/printf::backslash >/dev/null 2>&1 || . -AL completion/printf; then command -f completion/printf::backslash tr fi ;; esac } function completion/tr::class { typeset word="$TARGETWORD" case $word in (*\[:*) PREFIX=${TARGETWORD%\[:*} #>># complete -T -P "$PREFIX" -D "alpha + digit" '[:alnum:]' complete -T -P "$PREFIX" -D "letters" '[:alpha:]' complete -T -P "$PREFIX" -D "space and tab" '[:blank:]' complete -T -P "$PREFIX" -D "control characters" '[:cntrl:]' complete -T -P "$PREFIX" -D "digits" '[:digit:]' complete -T -P "$PREFIX" -D "printable characters, not including space" '[:graph:]' complete -T -P "$PREFIX" -D "lowercase letters" '[:lower:]' complete -T -P "$PREFIX" -D "printable characters, including space" '[:print:]' complete -T -P "$PREFIX" -D "punctuations, not including space" '[:punct:]' complete -T -P "$PREFIX" -D "whitespaces, including blank and newline" '[:space:]' complete -T -P "$PREFIX" -D "uppercase letters" '[:upper:]' complete -T -P "$PREFIX" -D "hexadecimal digits" '[:xdigit:]' #<<# return 0 esac return 1 } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/trap000066400000000000000000000012321354143602500165610ustar00rootroot00000000000000# (C) 2010-2011 magicant # Completion script for the "trap" built-in command. function completion/trap { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "p --print; print current trap settings" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset i=1 print=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i++]} in (-p|--print) print=true ;; (--) break ;; esac done if $print || [ $i -le ${WORDS[#]} ]; then complete --signal complete -P "$PREFIX" EXIT else complete -T -c fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/tree000066400000000000000000000055311354143602500165600ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "tree" command. # Supports tree 1.7.0. function completion/tree { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A; enable ANSI line graphics hack for indentation" "a; print all files (including dot-starting filenames)" "C; print filenames in color" "c; print or sort by last status change time" "--charset:; specify a character set to print in" "D; prepend last modified time to filenames" "d; print directories only" "--device; prepend device number to filenames" "--dirsfirst; list directories before other files" "--du; print the total sizes of directories" "F; append symbols indicating file type" "f; print pathnames instead of filenames" "--filelimit:; don't descend directories with more than the specified number of files" "g; prepend group name to filenames" "H:; print in HTML with the specified base URL" "h; print size using K, M, etc. for 1024^n" "I:; specify a pattern to exclude from printed filenames" "i; don't indent filenames" "--ignore-case; make pattern matching case-insensitive (with -I/-P)" "--inodes; prepend inode number to filenames" "J; print in JSON" "L:; specify directory depth to limit printing" "l; follow all symbolic links" "--matchdirs; apply patterns to directory names as well as file names" "N; print unprintable characters as is" "n; disable colored output" "--nolinks; disable hyperlinks in HTML output" "--noreport; don't print count of directories and files" "o:; specify a file to print to" "P:; specify a pattern to filter printed filenames" "p; prepend permissions to filenames" "--prune; exclude empty directories" "Q; double-quote filenames" "q; print unprintable characters as ?" "r; sort in reverse order" "S; like --charset=IBM437" "s; prepend file size to filenames" "--si; print size using k, M, etc. for 1000^n" "--sort::; specify a sort key" "T:; specify a title for HTML output" "t; sort by last modified time" "--timefmt:; specify a format for printing date" "U; don't sort" "u; prepend user name to filenames" "v; sort filenames regarding as version numbers" "X; print in XML" "x; don't search different file systems" "--help" "--version" ) #<# command -f completion//parseoptions -en case $ARGOPT in (-) command -f completion//completeoptions ;; ([HL]|--filelimit) ;; (--charset) # TODO --charset ;; (--sort) #>># complete -P "$PREFIX" name size complete -P "$PREFIX" -D "last status change time" ctime complete -P "$PREFIX" -D "last modified time" mtime complete -P "$PREFIX" -D "name as version number" version ;; #<<# (--timefmt) if command -vf completion//completestrftime >/dev/null 2>&1 || . -AL completion/date; then command -f completion//completestrftime fi ;; ('') complete -P "$PREFIX" -S / -T -d ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/type000066400000000000000000000003301354143602500165720ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "type" built-in command. function completion/type { WORDS=(command -V "${WORDS[2,-1]}") command -f completion//reexecute } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/typeset000066400000000000000000000034661354143602500173230ustar00rootroot00000000000000# (C) 2010-2018 magicant # Completion script for the "typeset" built-in command. # Completion function "completion/typeset" is used for the "export", "local" # and "readonly" built-ins as well. function completion/typeset { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "p --print; print specified variables or functions" "X --unexport; cancel exportation of variables" "--help" ) #<# if [ "${WORDS[1]}" = "typeset" ]; then OPTIONS=("$OPTIONS" #># "g --global; define global variables" ) #<# fi if [ "${WORDS[1]}" = "readonly" ] || [ "${WORDS[1]}" = "typeset" ]; then OPTIONS=("$OPTIONS" #># "f --functions; define or print functions rather than variables" ) #<# fi if [ "${WORDS[1]}" != "export" ]; then OPTIONS=("$OPTIONS" #># "x --export; export variables or print exported variables" ) #<# fi if [ "${WORDS[1]}" != "readonly" ]; then OPTIONS=("$OPTIONS" #># "r --readonly; define or print read-only variables or functions" ) #<# fi command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset i=1 func=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i++]} in (-f|--functions) func=true ;; (--) break ;; esac done if $func; then complete --function else case $TARGETWORD in (*=*:*) # remove until the last colon after the first '=' and # complete as a filename. If $word is # PATH=/bin:/usr/bin:/u for example, we complete the # filename /u PREFIX=${TARGETWORD%"${${TARGETWORD#*=}##*:}"} complete -T -P "$PREFIX" -S : -f ;; (*=*) # remove until '=' and complete as a filename. PREFIX=${TARGETWORD%"${TARGETWORD#*=}"} complete -P "$PREFIX" -f ;; (*) complete -T -S = -v ;; esac fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/ulimit000066400000000000000000000047001354143602500171210ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "ulimit" built-in command. function completion/ulimit { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "H --hard; set or print the hard limit" "S --soft; set or print the soft limit" "a --all; print all resource types with the current limits" "--help" ) #<# typeset limits="$(command -b ulimit -a 2>/dev/null)" case $limits in (*'-c: '*) OPTIONS=("$OPTIONS" #># "c --core; maximum size of core files in 512-byte blocks" ) #<# esac case $limits in (*'-d: '*) OPTIONS=("$OPTIONS" #># "d --data; maximum size of a process's data segment in kilobytes" ) #<# esac case $limits in (*'-e: '*) OPTIONS=("$OPTIONS" #># "e --nice; maximum scheduling priority (nice value)" ) #<# esac case $limits in (*'-f: '*) OPTIONS=("$OPTIONS" #># "f --fsize; maximum size of files created by a process in 512-byte blocks" ) #<# esac case $limits in (*'-i: '*) OPTIONS=("$OPTIONS" #># "i --sigpending; maximum number of pending signals" ) #<# esac case $limits in (*'-l: '*) OPTIONS=("$OPTIONS" #># "l --memlock; maximum memory size that can be locked into RAM (in kilobytes)" ) #<# esac case $limits in (*'-m: '*) OPTIONS=("$OPTIONS" #># "m --rss; maximum size of a process's resident set (in kilobytes)" ) #<# esac case $limits in (*'-n: '*) OPTIONS=("$OPTIONS" #># "n --nofile; maximum file descriptor + 1" ) #<# esac case $limits in (*'-q: '*) OPTIONS=("$OPTIONS" #># "q --msgqueue; maximum size of POSIX message queues (in bytes)" ) #<# esac case $limits in (*'-r: '*) OPTIONS=("$OPTIONS" #># "r --rtprio; maximum real-time scheduling priority" ) #<# esac case $limits in (*'-s: '*) OPTIONS=("$OPTIONS" #># "s --stack; maximum stack size (in kilobytes)" ) #<# esac case $limits in (*'-t: '*) OPTIONS=("$OPTIONS" #># "t --cpu; CPU time that can be used by a process (in seconds)" ) #<# esac case $limits in (*'-u: '*) OPTIONS=("$OPTIONS" #># "u --nproc; maximum number of processes for a user" ) #<# esac case $limits in (*'-v: '*) OPTIONS=("$OPTIONS" #># "v --as; maximum size of memory used by a process (in kilobytes)" ) #<# esac case $limits in (*'-x: '*) OPTIONS=("$OPTIONS" #># "x --locks; maximum number of file locks" ) #<# esac command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) #>># complete unlimited ;; #<<# esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/umask000066400000000000000000000010111354143602500167260ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "umask" built-in command. function completion/umask { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "S --symbolic; produce a symbolic output" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) if command -vf completion/chmod::mode >/dev/null 2>&1 || . -AL completion/chmod; then command -f completion/chmod::mode umask fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/unalias000066400000000000000000000006031354143602500172500ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "unalias" built-in command. function completion/unalias { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a --all; remove all aliases" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -a ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/uname000066400000000000000000000033111354143602500167200ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "uname" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/uname { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "a ${long:+--all}; print all information" "m ${long:+--machine}; print the hardware type" "n ${long:+--nodename}; print the network host name" "r ${long:+--kernel-release}; print the OS release level" "s ${long:+--kernel-name}; print the operating system name" "v ${long:+--kernel-version}; print the OS version" ) #<# case $type in (GNU|SunOS) OPTIONS=("$OPTIONS" #># "i ${long:+--hardware-platform}; print the hardware platform name" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "o --operating-system; print the operating system name" "--help" "--version" ) #<# ;; (SunOS) OPTIONS=("$OPTIONS" #># "X; print expanded system information" ) #<# ;; esac ;; (FreeBSD) OPTIONS=("$OPTIONS" #># "i; print the OS kernel ident" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "i; print the machine identification number" "l; print the OS license level" ) #<# ;; esac case $type in (GNU|*BSD|Darwin|SunOS) OPTIONS=("$OPTIONS" #># "p ${long:+--processor}; print the processor architecture type" ) #<# esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (*) ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/uniq000066400000000000000000000033111354143602500165670ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "uniq" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/uniq { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c ${long:+--count}; print repeat count for each unique line" "d ${long:+--repeated}; print repeated lines only" "f: ${long:+--skip-fields:}; skip first n fields on each line in comparison" "s: ${long:+--skip-chars:}; skip first n characters on each line in comparison" "u ${long:+--unique}; don't print repeated lines" ) #<# case $type in (GNU|FreeBSD|Darwin) OPTIONS=("$OPTIONS" #># "i ${long:+--ignore-case}; case-insensitive comparison" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "D; print only repeated lines in all" "--all-repeated::; print only repeated lines in all" "w: --check-chars:; compare at most n characters on each line" "z --zero-terminated; use null bytes as the line separator" "--help" "--version" ) #<# esac esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--all-repeated) #>># complete -P "$PREFIX" -D "don't delimit group of repeated lines" none complete -P "$PREFIX" -D "print a newline before each group of repeated lines" prepend complete -P "$PREFIX" -D "print a newline between groups of repeated lines" separate ;; #<<# ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/unset000066400000000000000000000012431354143602500167530ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "unset" built-in command. function completion/unset { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "f --functions; remove functions" "v --variables; remove variables" "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) typeset i=1 func=false while [ $i -le ${WORDS[#]} ]; do case ${WORDS[i++]} in (-f|--functions) func=true ;; (-v|--variables) func=false ;; (--) break ;; esac done if $func; then complete --function else complete -v fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/useradd000066400000000000000000000077001354143602500172500ustar00rootroot00000000000000# (C) 2011 magicant # Completion script for the "useradd" command. # Supports shadow tool suite 4.1.4.2, OpenBSD 4.9, NetBSD 5.0, SunOS 5.11, # HP-UX 11i v3. function completion/useradd { typeset type="$(uname 2>/dev/null)" case $type in (Linux) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "b: ${long:+--base-dir:}; specify the directory the home directory is created in" "c: ${long:+--comment:}; specify a comment (typically the user's full name)" "d: ${long:+--home:}; specify the home directory" "D ${long:+--defaults}; print or show the default settings" "e: ${long:+--expiredate:}; specify the date the account expires on" "f: ${long:+--inactive:}; specify the date the password expires on" "G: ${long:+--groups:}; specify supplementary groups" "g: ${long:+--group:}; specify the group" "k: ${long:+--skel:}; specify the skeleton directory" "m ${long:+--create-home}; create the home directory" "o ${long:+--non-unique}; allow assigning one user ID to more than one user name" "s: ${long:+--shell:}; specify the login shell" "u: ${long:+--uid:}; specify the user ID number" ) #<# case $type in (Linux|SunOS) OPTIONS=("$OPTIONS" #># "K: ${long:+--key:}; specify an option to override the default" ) #<# ;; esac case $type in (SunOS) OPTIONS=("$OPTIONS" #># "p:; specify a project" ) #<# ;; (*) OPTIONS=("$OPTIONS" #># "p: ${long:+--password:}; specify the encrypted password" ) #<# ;; esac case $type in (Linux) OPTIONS=("$OPTIONS" #># "h --help; print help" "l --no-log-init; don't add the user to the lastlog and faillog databases" "M; don't create the home directory" "N --no-user-group; don't create the user's own group" "r --system; create a system account" "U --user-group; create the user's own group" "Z: --selinux-user:; specify the SELinux user" ) #<# ;; (*BSD) OPTIONS=("$OPTIONS" #># "L:; specify the login class" "v; print operations that are executed" ) #<# case $type in (OpenBSD) OPTIONS=("$OPTIONS" #># "r:; specify the integer range from which the new user ID is chosen" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "F; force the user to change the password on next login" "M:; specify the permission of the home directory" "S; allow samba user names with a trailing dollar sign to be added to the system" ) #<# ;; esac ;; (SunOS) OPTIONS=("$OPTIONS" #># "A:; specify authorizations" "P:; specify execution profiles" "R:; specify execution roles" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "i; inherit the existing home directory" "O:; specify if the -o option is enabled by default" "r:; specify if the existing home directory's permission is reset" "S:; specify the alternate password file of NIS" "t:; specify the template file containing default settings" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; ([bdk]|--base-dir|--home|--skel) complete -T -P "$PREFIX" -S / -d ;; (A) # TODO ;; # (c|--comment) # ;; (e|--expiredate) #TODO (f|--inactive) #TODO ;; (G|--groups) PREFIX="${TARGETWORD%"${${TARGETWORD#"$PREFIX"}##*,}"}" complete -T -P "$PREFIX" -S , -g ;; (g|--group) complete -P "$PREFIX" -g ;; (K|--key) # TODO ;; (L) # TODO ;; # (M) # ;; (O) complete -P "$PREFIX" -D "yes" yes complete -P "$PREFIX" -D "no" yes ;; (P) # TODO ;; (p) # TODO ;; (R) # TODO ;; (r) case $type in (HP-UX) complete -P "$PREFIX" -D "yes" yes complete -P "$PREFIX" -D "no" yes ;; esac ;; (S) # TODO ;; (s|--shell) complete -P "$PREFIX" -- $(sed -e 's/#.*//' /etc/shells 2>/dev/null) || { complete -T -P "$PREFIX" -S / -d complete -P "$PREFIX" --executable-file } ;; (t) complete -P "$PREFIX" -f ;; # (u|--uid) # ;; (Z|--selinux-user) # TODO ;; ('') complete -u ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/valgrind000066400000000000000000000172661354143602500174370ustar00rootroot00000000000000# (C) 2015 magicant # Completion script for the "valgrind" command. # Supports Valgrind 3.10.0. function completion/valgrind { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--alignment:" "--allow-mismatched-debuginfo:" "--aspace-minaddr:; specify min address to allocate for Valgrind" "--child-silent-after-fork:" "--db-attach:; ask whether to invoke debugger on error" "--db-command:; specify debugger to use with --db-attach" "--debuginfo-server:; specify address/port pair to connect to valgrind-di-server" "--demangle:" "--dsymutil:; run dsymutil automatically" "--error-exitcode:; specify exit status to return on error" "--error-limit:; limit number of errors to report" "--extra-debuginfo-path:; specify location of debug info" "--fair-sched:; use fair internal scheduling policy" "--fullpath-after:; show path after specified string" "--gen-suppressions:; generate suppressions for reported errors" "h --help; show help" "--help-debug; show help with debugging info" "--input-fd:; specify file descriptor to read reply" "--kernel-variant:" "--log-fd:; specify file descriptor to print messages" "--log-file:; specify file name to print messages" "--log-socket:; specify address/port pair to send messages" "--main-stacksize:; specify main thread stack size" "--max-stackframe:; specify max stack frame size" "--merge-recursive-frames:; specify stack level to detect recursion" "--num-callers:; specify max call stack level to print" "--num-transtab-sectors:; specify number of sectors for code translation" "q --quiet; print error messages only" "--read-inline-info:; recognize inlined functions" "--read-var-info:; recognize variable types and locations" "--redzone-size:; specify size of padding around allocated memory" "--require-text-symbol:; specify so file and symbol name pattern pair to require when loaded" "--run-libc-freeres:; make libc release internal resources before exit" "--show-below-main:" "--show-emwarns:" "--sigill-diagnostics:; report illegal instructions" "--sim-hints:; specify hints to modify simulation behavior" "--smc-check:; specify where self-modifying code is detected" "--soname-synonyms:; specify patterns to replace libraries" "--suppressions:; specify file that defines suppressed errors" "--time-stamp:; print messages with time stamp" "--tool:" "--trace-children:; keep tracing even after 'exec'" "--trace-children-skip:; specify command name patterns to stop tracing" "--trace-children-skip-by-arg:; specify argument patterns to stop tracing" "--track-fds:; track file descriptors" "--unw-stack-scan-frames:; specify call stack level to scan" "--unw-stack-scan-thresh:; specify threshold to force stack scanning" "--version" "v --verbose; show more detail" "--vgdb:; enable gdbserver connection" "--vgdb-error:; specify number of errors allowed before gdbserver connection" "--vgdb-poll:; specify gdbserver poll count" "--vgdb-prefix:; specify prefix of name of temporary files to communicate with gdbserver" "--vgdb-stop-at:; specify events to wait for gdbserver connection" "--xml:; use XML format for messages" "--xml-fd:; specify file descriptor to print XML messages" "--xml-file:; specify file name to print XML messages" "--vgdb-shadow-registers:; expose shadow registers to gdbserver" "--xml-socket:; specify address/port pair to send XML messages" "--xml-user-comment:; specify additional comment added in XML format" ) #<# # memcheck options OPTIONS=("$OPTIONS" #># "--errors-for-leak-kinds:" "--free-fill:; specify dummy byte value to fill freed memory" "--freelist-vol:; specify size of freed memory" "--freelist-big-blocks:; specify size threshold to be prioritized in free list" "--ignore-ranges:; specify memory range to ignore" "--keep-stacktraces:; specify stack trace recording policy" "--leak-check:; specify leak check verbosity" "--leak-check-heuristics:; specify heuristics to use" "--leak-resolution:; specify stack trace similarity to allow merge" "--malloc-fill:; specify initial byte value to fill allocated memory" "--partial-loads-ok:; allow accessing partially invalid address range" "--show-mismatched-frees:; check malloc/new/free/delete combination" "--show-leak-kinds:" "--show-possibly-lost:; report possibly lost leaks" "--show-reachable:; report all kinds of leaks" "--track-origins:; track origin of uninitialized values" "--undef-value-errors:; report uses of uninitialized values" "--workaround-gcc296-bugs: overlook access below stack pointer" ) #<# # TODO: Other tools' options are not supported now... command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (--allow-mismatched-debuginfo|--child-silent-after-fork|--db-attach|--demangle|--dsymutil|--error-limit|--partial-loads-ok|--read-inline-info|--read-var-info|--run-libc-freeres|--show-mismatched-frees|--show-possibly-lost|--show-reachable|--sigill-diagnostics|--time-stamp|--trace-children|--track-fds|--track-origins|--undef-value-errors|--vgdb-shadow-registers|--xml) #>># complete -P "$PREFIX" no yes ;; #<<# (--gen-suppressions) #>># complete -P "$PREFIX" all no yes ;; #<<# (--vgdb) #>># complete -P "$PREFIX" full no yes ;; #<<# (--fair-sched) #>># complete -P "$PREFIX" -D 'enable or die' yes complete -P "$PREFIX" -D 'enable or fall back' try complete -P "$PREFIX" -D 'disable' no ;; #<<# (--fullpath-after|--log-file|--suppressions|--xml-file) complete -P "$PREFIX" -f ;; (--extra-debuginfo-path) complete -P "$PREFIX" -S / -T -d ;; (--db-command) complete -P "$PREFIX" --external-command ;; (--keep-stacktraces) #>># complete -P "$PREFIX" \ alloc alloc-and-free alloc-then-free free none ;; #<<# (--kernel-variant) #>># complete -P "$PREFIX" android-gpu-adreno3xx complete -P "$PREFIX" android-gpu-sgx5xx complete -P "$PREFIX" -D 'software TLS' android-no-hw-tls complete -P "$PREFIX" -D 'sys_broc on x86' bproc ;; #<<# (--leak-check) #>># complete -P "$PREFIX" -D 'full' full yes complete -P "$PREFIX" no complete -P "$PREFIX" summary ;; #<<# (--leak-check-heuristics) command -f completion//valgrind:skipcomma #>># complete -P "$PREFIX" all length64 multipleinheritance \ newarray none stdstring ;; #<<# (--leak-resolution) #>># complete -P "$PREFIX" high low med ;; #<<# (--show-leak-kinds|--errors-for-leak-kinds) command -f completion//valgrind:skipcomma #>># complete -P "$PREFIX" \ all definite indirect none possible reachable ;; #<<# (--sim-hints) command -f completion//valgrind:skipcomma #>># complete -P "$PREFIX" enable-outer complete -P "$PREFIX" fuse-compatible complete -P "$PREFIX" lax-ioctls complete -P "$PREFIX" no-inner-prefix complete -P "$PREFIX" no-nptl-pthread-stackcache ;; #<<# (--smc-check) #>># complete -P "$PREFIX" all complete -P "$PREFIX" -D 'all but file-backed memory' all-non-file complete -P "$PREFIX" none complete -P "$PREFIX" -D 'on stack only' stack ;; #<<# (--trace-children-skip) command -f completion//valgrind:skipcomma complete -P "$PREFIX" --external-command ;; (--trace-children-skip-by-arg) command -f completion//valgrind:skipcomma complete -P "$PREFIX" -f ;; (--tool) #>># complete -P "$PREFIX" cachegrind callgrind drd exp-bbv exp-dhat\ exp-sgcheck helgrind lackey massif memcheck none ;; #<<# (--vgdb-stop-at) command -f completion//valgrind:skipcomma #>># complete -P "$PREFIX" all exit none startup valgrindabexit ;; #<<# ('') command -f completion//getoperands command -f completion//reexecute -e ;; esac } # skip to last comma-separated word function completion//valgrind:skipcomma { typeset targetword="${TARGETWORD#"$PREFIX"}" targetword="${targetword##*,}" PREFIX="${TARGETWORD%"$targetword"}" } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/vi000066400000000000000000000004021354143602500162270ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "vi" command. # Supports POSIX 2008, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, SunOS 5.10, # HP-UX 11i v3. function completion/vi { command -f completion//reexecute ex } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/view000066400000000000000000000004061354143602500165670ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "view" command. # Supports POSIX 2008, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, SunOS 5.10, # HP-UX 11i v3. function completion/view { command -f completion//reexecute ex } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/vim000066400000000000000000000066401354143602500164160ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "vim" command. # Supports Vim 7.3. function completion/vim { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "A; Arabic mode" "b; binary mode" "C; enable the compatible option" "c:; specify a command to be executed after starting up" "D; debug mode" "d; diff mode" "E; operate as the ex editor (improved Ex mode)" "e; operate as the ex editor" "F; Farsi mode" "f --nofork; don't start a new process to execute the GUI" "g; operate in GUI mode" "H; Hebrew mode" "h --help; print help" "i:; specify the viminfo filename" "l; lisp mode" "M; disable the modifiable and write options" "m; disable the write option" "N; disable the compatible option" "n; don't use any swap files" "O::; specify the number of windows to open (vertical split)" "o::; specify the number of windows to open" "p::; specify the number of tab pages to open" "q:; specify a quickfix error file to read" "R; read-only mode" "r L; recover unsaved files" "S:; specify a file to be sourced after starting up" "T:; specify the terminal type" "t:; specify an identifier to jump" "U:; specify the gvimrc filename" "u:; specify the vimrc filename" "V::; specify the value of the verbose option" "v; operate as the vi editor" "w:; specify the value of the window option" "X; don't connect to the X server" "x; enable encryption" "y; easy mode" "Z; restricted mode" "--cmd:; specify a command to be executed before processing vimrc" "--echo-wid; print GTK+ GUI window ID" "--literal; don't expand wild card characters in filename arguments" "--noplugin; don't load plug-ins" "--remote; open files in the remote Vim server" "--remote-silent; like --remote, but don't complain if there is no server" "--remote-wait; like --remote, but wait for the editing to be finished" "--remote-wait-silent; like --remote-wait and --remote-silent" "--remote-tab; like --remote, but open each file in a new tab page" "--remote-tab-silent; like --remote-tab and --remote-silent" "--remote-tab-wait; like --remote-tab and --remote-wait" "--remote-tab-wait-silent; like --remote-tab and --remote-wait-silent" "--remote-expr:; specify an expression to be evaluated on the remote server" "--remote-send:; specify a key sequence to send to the remote server" "--role:; specify a role for GTK+ 2 GUI" "--serverlist; print list of servers" "--servername:; specify a server name to operate as or connect to" "--startuptime:; specify a file to write timing messages while starting up" "--version" ) #<# case ${WORDS[1]##*/} in (*ex*) OPTIONS=("$OPTIONS" #># "s; non-interactive batch processing mode" ) #<# ;; (*) OPTIONS=("$OPTIONS" #># "s:; specify a file to be source!ed after starting up" ) #<# ;; esac command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions -e ;; # ([cOopVw]|--cmd|--remote-expr|--remote-send|--role|--servername) # ;; ([iSsUu]|--startuptime) if [ "$PREFIX" ]; then complete -P "$PREFIX" "" else complete -f fi ;; (q) complete -P "$PREFIX" -f ;; (T) if [ "$PREFIX" ]; then complete -P "$PREFIX" "" else typeset term desc while read -r term desc; do complete -D "$desc" -- "$term" done 2>/dev/null <(toe -a || toe) fi ;; (t) if [ -r tags ]; then complete -P "$PREFIX" -R "!_TAG_*" -- \ $(cut -f 1 tags 2>/dev/null) fi ;; ('') complete -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/vimdiff000066400000000000000000000003101354143602500172330ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "vimdiff" command. # Supports Vim 7.3. function completion/vimdiff { command -f completion//reexecute vim } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/wait000066400000000000000000000012111354143602500165540ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "wait" built-in command. function completion/wait { typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "--help" ) #<# command -f completion//parseoptions -es case $ARGOPT in (-) command -f completion//completeoptions ;; (*) case $TARGETWORD in (%*) # complete job name complete -P % -j ;; (*) # complete job process ID typeset pid status while read -r pid status; do complete -D "$(ps -p $pid -o args=)" -- $pid done 2>/dev/null <(jobs -l | sed -e 's/^\[[[:digit:]]*\][[:blank:]]*[-+]//') ;; esac ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/watch000066400000000000000000000020211354143602500167160ustar00rootroot00000000000000# (C) 2016 magicant # Completion script for the "watch" command. # Supports procps-ng 3.3.10. function completion/watch { typeset OPTIONS ARGOPT PREFIX OPTIONS=( "b --beep; beep when the command returns a non-zero exit status" "c --color; interpret ANSI color sequences" "d:: --differences::; highlight the differences from previous output" "e --errexit; stop when the command returns a non-zero exit status" "g --chgexit; exit when command output differs from the previous" "h --help; print help" "n: --interval:; specify update interval in seconds" "p --precise; keep update interval precise" "t --no-title; turn off the header" "v --version; print version info" "x --exec; don't interpret operands as shell script" ) #<# command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; (d|--differences) complete -P "$PREFIX" permanent ;; ('') command -f completion//getoperands command -f completion//reexecute -e ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/wc000066400000000000000000000024071354143602500162310ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "wc" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/wc { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "c ${long:+--bytes}; print byte counts" "l ${long:+--lines}; print line counts" "m ${long:+--chars}; print character counts" "w ${long:+--words}; print word counts" ) #<# case $type in (GNU|FreeBSD|NetBSD) OPTIONS=("$OPTIONS" #># "L ${long:+--max-line-length}; print longest line lengths" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "--files0-from:; specify a file containing null-separated file names to count size" "--help" "--version" ) #<# esac ;; (OpenBSD) OPTIONS=("$OPTIONS" #># "h; print size using K, M, etc. for 1024^n" ) #<# ;; esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -P "$PREFIX" -f ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/which000066400000000000000000000031121354143602500167140ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "which" command. # Supports GNU which 2.20, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/which { case $("${WORDS[1]}" --version 2>/dev/null) in (*'GNU'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># ) #<# case $type in (GNU|Darwin|*BSD) OPTIONS=("$OPTIONS" #># "a ${long:+--all}; print all matches, not just the first one" ) #<# case $type in (Darwin|FreeBSD) OPTIONS=("$OPTIONS" #># "s; print nothing" ) #<# esac ;; esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "i --read-alias; read alias definitions from the standard input" "--skip-alias; cancel the --read-alias option" "--read-functions; read function definitions from the standard input" "--skip-functions; cancel the --read-functions option" "--skip-dot; ignore directory names that start with a dot" "--skip-tilde; ignore directories under \$HOME" "--show-dot; print \"./program\" for programs in a dot directory" "--show-tilde; use ~ for programs under \$HOME" "V v --version; print version info" "--help" ) #<# # --tty-only is not completed since it's meaningless in an # interactive shell session esac command -f completion//parseoptions ${long:+-es} case $ARGOPT in (-) command -f completion//completeoptions ;; (*) complete -P "$PREFIX" --external-command ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/who000066400000000000000000000041311354143602500164110ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "who" command. # Supports POSIX 2008, GNU coreutils 8.6, FreeBSD 8.1, OpenBSD 4.8, NetBSD 5.0, # Mac OS X 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/who { case $("${WORDS[1]}" --version 2>/dev/null) in (*'coreutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "H ${long:+--heading}; print the header line" "m; print info about the current user only" "q ${long:+--count}; print the number of users on the system with their names" "T ${long:+w --message --mesg --writable}; print if each terminal accepts write messages" "u; print how long the user has been idle" ) #<# case $type in (GNU|Darwin|NetBSD|SunOS|HP-UX) OPTIONS=("$OPTIONS" #># "a ${long:+--all}; print all information" "b ${long:+--boot}; print last system boot time" "d ${long:+--dead}; print info about dead processes" "l ${long:+--login}; print terminals not used by users" "p ${long:+--process}; print active processes spawned by init" "r ${long:+--runlevel}; print runlevel" "s ${long:+--short}; print name, line, and time fields" "t ${long:+--time}; print last system clock change time" ) #<# esac case $type in (GNU) OPTIONS=("$OPTIONS" #># "--lookup; canonicalize host names via DNS lookup" "--help" "--version" ) #<# ;; (NetBSD) OPTIONS=("$OPTIONS" #># "v; print more info (with -u)" ) #<# ;; (SunOS) OPTIONS=("$OPTIONS" #># "n:; specify the number of users to print per line" ) #<# ;; (HP-UX) OPTIONS=("$OPTIONS" #># "A; print when accounting system was turned on/off" "R; print host name" "W; use /var/adm/wtmps" ) #<# ;; esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ('') command -f completion//getoperands if [ ${WORDS[#]} -eq 1 ] && [ "${WORDS[1]}" = am ]; then complete i elif [ ${WORDS[#]} -eq 0 ]; then complete -f am fi ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/xargs000066400000000000000000000050421354143602500167420ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "xargs" command. # Supports POSIX 2008, GNU findutils 4.5.9, FreeBSD 8.1, OpenBSD 4.8, # NetBSD 5.0, Darwin 10.6.4, SunOS 5.10, HP-UX 11i v3. function completion/xargs { case $("${WORDS[1]}" --version 2>/dev/null) in (*'findutils'*) typeset type=GNU ;; (*) typeset type="$(uname 2>/dev/null)" ;; esac case $type in (GNU) typeset long=true ;; (*) typeset long= ;; esac case $type in (GNU|SunOS|HP-UX) typeset short=true ;; (*) typeset short= ;; esac typeset OPTIONS ARGOPT PREFIX OPTIONS=( #># "E: ${short:+e::} ${long:+--eof::}; specify a string to treat as end of input" "I: ${short:+i::} ${long:+--replace::}; specify a string replaced by input words" "L: ${short:+l::} ${long:+--max-lines::}; specify the max number of input lines to use at once" "n: ${long:+--max-args:}; specify the max number of input words to use at once" "p ${long:+--interactive}; confirm before each command invocation" "s: ${long:+--max-chars:}; specify the max length of executed command lines in bytes" "t ${long:+--verbose}; print each executed command line before execution" "x ${long:+--exit}; abort when executed command line length exceeds the limit" ) #<# case $type in (GNU|*BSD|Darwin) OPTIONS=("$OPTIONS" #># "0 ${long:+--null}; assume input words are separated by null bytes" "P: ${long:+--max-procs:}; specify the max number of commands executed at once" ) #<# case $type in (GNU) OPTIONS=("$OPTIONS" #># "a: --arg-file:; specify the file to read words from" "d: --delimiter:; specify the delimiter that separates words" "--help" "--version" ) #<# esac case $type in (*BSD|Darwin) OPTIONS=("$OPTIONS" #># "J:; like -I, but replace the first exact match only" "o; reopen standard input as /dev/tty when executing commands" "R:; specify the max number of operands replaced in -I" ) #<# case $type in (FreeBSD|NetBSD) OPTIONS=("$OPTIONS" #># "S:; specify how long the command line can grow in -I" ) #<# esac esac case $type in (GNU|OpenBSD) OPTIONS=("$OPTIONS" #># "r ${long:+--no-run-if-empty}; don't execute the command at all if input is empty" ) #<# esac esac command -f completion//parseoptions case $ARGOPT in (-) command -f completion//completeoptions ;; ([dEeIiJLlnPRSs]|--delimiter|--eof|--replace|--max-*) ;; (a|--arg-file) complete -P "$PREFIX" -f ;; ('') command -f completion//getoperands command -f completion//reexecute -e ;; esac } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/completion/yash000066400000000000000000000002561354143602500165640ustar00rootroot00000000000000# (C) 2010 magicant # Completion script for the "yash" command. function completion/yash { command -f completion//reexecute set } # vim: set ft=sh ts=8 sts=8 sw=8 noet: yash-2.49/share/initialization/000077500000000000000000000000001354143602500165505ustar00rootroot00000000000000yash-2.49/share/initialization/common000066400000000000000000000221141354143602500177630ustar00rootroot00000000000000##### Common Yashrc ##### # This file is in the public domain. # enable bash-like extended expansion set --brace-expand # enable recursive pathname expansion set --extended-glob # prevent redirections from overwriting existing files set --no-clobber # don't implicitly expand non-existent variables to empty strings set --no-unset # if yash is built with command history enabled... if command --identify --builtin-command history >/dev/null; then # don't save commands starting with a space in history set --hist-space # prevent clearing history by accident history() if [ -t 0 ] && ( for arg do case "${arg}" in (-[drsw]?* | --*=*) ;; (-*c*) exit;; esac done false ) then printf 'history: seems you are trying to clear the whole history.\n' >&2 printf 'are you sure? (yes/no) ' >&2 case "$(head -n 1)" in ([Yy]*) command history "$@";; (*) printf 'history: cancelled.\n' >&2;; esac else command history "$@" fi fi # if yash is built with line-editing enabled... if command --identify --builtin-command bindkey >/dev/null; then # print job status update ASAP, but only while line-editing set --notify-le # some terminfo data are broken; meta flags have to be ignored for UTF-8 set --le-no-conv-meta # enable command line prediction set --le-predict # most users are more familiar with emacs mode than vi mode if [ -o vi ]; then set --emacs fi # some useful key bindings bindkey --emacs '\^N' beginning-search-forward bindkey --emacs '\^O' clear-candidates bindkey --emacs '\^P' beginning-search-backward bindkey --emacs '\N' complete-next-column bindkey --emacs '\P' complete-prev-column # key bindings for vi mode, some of which are from emacs mode bindkey --vi-insert '\^A' beginning-of-line bindkey --vi-insert '\^B' backward-char bindkey --vi-insert '\^D' eof-or-delete bindkey --vi-insert '\#' eof-or-delete bindkey --vi-insert '\^E' end-of-line bindkey --vi-insert '\^F' forward-char bindkey --vi-insert '\^K' forward-kill-line bindkey --vi-insert '\^N' beginning-search-forward bindkey --vi-insert '\^O' clear-candidates bindkey --vi-insert '\^P' beginning-search-backward bindkey --vi-insert '\^U' backward-kill-line bindkey --vi-insert '\$' backward-kill-line bindkey --vi-insert '\^W' backward-delete-viword bindkey --vi-insert '\^Y' put-left bindkey --vi-insert '\N' complete-next-column bindkey --vi-insert '\P' complete-prev-column bindkey --vi-command '\^N' beginning-search-forward bindkey --vi-command '\^P' beginning-search-backward fi # some useful shortcuts alias -- -='cd -' alias la='ls -a' ll='ls -l' lla='ll -a' alias r='fc -s' # avoid removing/overwriting existing files by accident cp() if [ -t 0 ]; then command cp -i "$@"; else command cp "$@"; fi mv() if [ -t 0 ]; then command mv -i "$@"; else command mv "$@"; fi rm() if [ -t 0 ]; then command rm -i "$@"; else command rm "$@"; fi # normally yash is more POSIX-compliant than /bin/sh :-) sh() { yash --posix "$@"; } yash() { command yash "$@"; } # By re-defining 'yash' using the 'command' built-in, the 'jobs' built-in # prints a command name that exposes the arguments like # 'yash --posix -n foo.sh' rather than a command name that hides the # arguments like 'yash --posix "${@}"'. This applies to the 'yash' command # invoked via the 'sh' function. # ensure job control works as expected case $- in (*m*) trap - TSTP TTIN TTOU esac # if the terminal supports color... if [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then # make command output colorful if ls --color=auto -d / >/dev/null 2>&1; then ls() { command ls --color=auto "$@"; } fi if grep --color=auto -q X <</dev/null; then grep() { command grep --color=auto "$@"; } fi if ggrep --color=auto -q X <</dev/null; then ggrep() { command ggrep --color=auto "$@"; } fi fi # if vim is available... if command --identify vim >/dev/null 2>&1; then # prefer vim over vi vi() { vim "$@"; } view() { vim -R "$@"; } vim() { command vim "$@"; } # Re-definition hack. See above. fi # an alias that opens a file if command --identify xdg-open >/dev/null 2>&1; then alias o='xdg-open' elif command --identify cygstart >/dev/null 2>&1; then alias o='cygstart' elif [ "$(uname)" = Darwin ] 2>/dev/null; then alias o='open' fi # define some basic variables if missing : ${PAGER:=less} ${EDITOR:=vi} ${FCEDIT:=$EDITOR} : ${LOGNAME:=$(logname)} ${HOSTNAME:=$(uname -n)} # disable confusing treatment of arguments in the echo command : ${ECHO_STYLE:=RAW} # variables needed for command history HISTFILE=~/.yash_history HISTSIZE=5000 # HISTRMDUP makes prediction less accurate # HISTRMDUP=500 # default mail check interval is too long MAILCHECK=0 # emulate bash's $SHLVL if [ "${_old_shlvl+set}" != set ]; then _old_shlvl=${SHLVL-} fi SHLVL=$((_old_shlvl+1)) 2>/dev/null || SHLVL=1 export SHLVL # initialize event handlers COMMAND_NOT_FOUND_HANDLER=() PROMPT_COMMAND=() YASH_AFTER_CD=() # define prompt if [ -n "${SSH_CONNECTION-}" ]; then _hc='\fy.' # yellow hostname for SSH remote else _hc='\fg.' # green hostname for local fi if [ "$(id -u)" -eq 0 ]; then _uc='\fr.' # red username for root _2c='\fr.' # red PS2 for root else _uc=$_hc _hc= # same username color as hostname for non-root user _2c= # PS2 in normal color for non-root user fi # The main prompt ($YASH_PS1) contains the username, hostname, working # directory, last exit status (only if non-zero), and $SHLVL (only if # non-one). YASH_PS1=$_uc'${LOGNAME}'$_hc'@${HOSTNAME%%.*}\fd. '\ '${${${PWD:/~/\~}##*/}:-$PWD} ${{?:/0/}:+\\fr.$?\\fd. }${{SHLVL-0}:/1}\$ ' YASH_PS1R='\fc.${_vcs_info}' YASH_PS1S='\fo.' YASH_PS2=$_2c'> ' #YASH_PS2R= YASH_PS2S=$YASH_PS1S YASH_PS4='\fm.+ ' YASH_PS4S='\fmo.' unset _hc _uc _2c # No escape sequences allowed in the POSIXly-correct mode. PS1='${LOGNAME}@${HOSTNAME%%.*} '$PS1 # find escape sequence to change terminal window title case "$TERM" in (xterm|xterm[+-]*|gnome|gnome[+-]*|putty|putty[+-]*|cygwin) _tsl='\033];' _fsl='\a' ;; (*) _tsl=$( (tput tsl 0; echo) 2>/dev/null | sed -e 's;\\;\\\\;g' -e 's;;\\033;g' -e 's;;\\a;g' -e 's;%;%%;g') _fsl=$( (tput fsl ; echo) 2>/dev/null | sed -e 's;\\;\\\\;g' -e 's;;\\033;g' -e 's;;\\a;g' -e 's;%;%%;g') ;; esac # if terminal window title can be changed... if [ "$_tsl" ] && [ "$_fsl" ]; then # set terminal window title on each prompt _set_term_title() if [ -t 2 ]; then printf "$_tsl"'%s@%s:%s'"$_fsl" "${LOGNAME}" "${HOSTNAME%%.*}" \ "${${PWD:/$HOME/\~}/#$HOME\//\~\/}" >&2 fi PROMPT_COMMAND=("$PROMPT_COMMAND" '_set_term_title') # reset window title when changing host or user ssh() { if [ -t 2 ]; then printf "$_tsl"'ssh %s'"$_fsl" "$*" >&2; fi command ssh "$@" } su() { if [ -t 2 ]; then printf "$_tsl"'su %s'"$_fsl" "$*" >&2; fi command su "$@" } sudo() { if [ -t 2 ]; then printf "$_tsl"'sudo %s'"$_fsl" "$*" >&2; fi command sudo "$@" } fi # define function that updates $_vcs_info and $_vcs_root _update_vcs_info() { typeset type branch { read --raw-mode type read --raw-mode _vcs_root read --raw-mode branch } <( exec 2>/dev/null typeset COMMAND_NOT_FOUND_HANDLER= while true; do if [ -e .git ] || [ . -ef "${GIT_WORK_TREE-}" ]; then printf 'git\n%s\n' "${GIT_WORK_TREE:-$PWD}" git branch --no-color | sed -n '/^\*/s/^..//p' exit elif [ -d .hg ]; then printf 'hg\n%s\n' "$PWD" exec cat .hg/branch elif [ -d .svn ]; then printf 'svn\n' _vcs_root=$(svn info --show-item=wc-root) printf '%s\n' "$_vcs_root" path=$(svn info --show-item=relative-url) case $path in (*/branches/*) printf '%s\n' "${${path#*/branches/}%%/*}" esac exit fi if [ / -ef . ] || [ . -ef .. ]; then exit fi cd -P .. done ) case "$type#$branch" in (hg#default) _vcs_info='hg';; (git#master) _vcs_info='git';; (*# ) _vcs_info="$type";; (* ) _vcs_info="$type@$branch";; esac } # update $_vcs_info on each prompt PROMPT_COMMAND=("$PROMPT_COMMAND" '_update_vcs_info') # these aliases choose a version controlling program for the current directory alias _vcs='${${_vcs_info:?not in a version-controlled directory}%%@*}' alias ci='_vcs commit' alias co='_vcs checkout' alias di='_vcs diff' alias log='_vcs log' alias st='_vcs status' alias up='_vcs update' # when a directory name is entered as a command, treat as "cd" _autocd() if [ -d "$1" ]; then HANDLED=true cd -- "$@" break -i fi COMMAND_NOT_FOUND_HANDLER=("$COMMAND_NOT_FOUND_HANDLER" '_autocd "$@"') # treat command names starting with % as "fg" _autofg() if [ $# -eq 1 ]; then case $1 in (%*) HANDLED=true fg "$1" break -i esac fi COMMAND_NOT_FOUND_HANDLER=("$COMMAND_NOT_FOUND_HANDLER" '_autofg "$@"') # print file type when executing non-executable files _file_type() if [ -e "$1" ] && ! [ -d "$1" ]; then file -- "$1" fi COMMAND_NOT_FOUND_HANDLER=("$COMMAND_NOT_FOUND_HANDLER" '_file_type "$@"') # vim: set et sw=2 sts=2 tw=78 ft=sh: yash-2.49/share/initialization/default000066400000000000000000000017501354143602500201220ustar00rootroot00000000000000##### Default Yashrc ##### # This file is automatically loaded when yash starts up and a user-specific # initialization script cannot be read from ~/.yashrc. # This file is in the public domain. # print introduction ( command . --autoload config 2>/dev/null || yashdatadir='?' cat <<__END__ Welcome to yash! You are seeing this message because a user-specific initialization script is not found at ~/.yashrc. The current session has been loaded with common settings that should be useful for many users. To suppress this message, copy a sample initialization script from ${yashdatadir}/initialization/sample to ~/.yashrc and make any customization in it if you want. For bare default settings, put an empty file at ~/.yashrc. Without ~/.yashrc, you will see this message again next time you start yash. For more instructions, see the manual at . __END__ ) # load a sample initialization script . --autoload initialization/sample # vim: set et sw=2 sts=2 tw=78 ft=sh: yash-2.49/share/initialization/sample000066400000000000000000000017051354143602500177570ustar00rootroot00000000000000##### Sample Yashrc ##### # This is a sample initialization script for yash. Copy this file to ~/.yashrc # and add your favorite customization to it. # Firstly, load the common customization script. # If you don't like settings applied in this script, remove this line. . --autoload --no-alias initialization/common # These are additional aliases that are not defined in the common script. # Uncomment to enable them. #alias g='grep' #alias l='$PAGER' #alias --global L='|$PAGER' #alias --global N='>/dev/null 2>&1' N1='>/dev/null' N2='2>/dev/null' # Uncomment to enable direnv support. (jq required) #_update_direnv() { # eval "$( # direnv export json | # jq -r 'to_entries | .[] | # if .value == null then # @sh "unset \(.key)" # else # @sh "export \(.key)=\(.value)" # end' # )" #} #_update_direnv #YASH_AFTER_CD=("$YASH_AFTER_CD" '_update_direnv') # And add your own customization below. # vim: set et sw=2 sts=2 tw=78 ft=sh: yash-2.49/sig.c000066400000000000000000001210241354143602500133450ustar00rootroot00000000000000/* Yash: yet another shell */ /* sig.c: signal handling */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "sig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_GETTEXT # include #endif #include "builtin.h" #include "exec.h" #include "expand.h" #include "job.h" #include "option.h" #include "parser.h" #include "redir.h" #include "siglist.h" #include "signum.h" #include "strbuf.h" #include "util.h" #include "yash.h" #if YASH_ENABLE_LINEEDIT # include "xfnmatch.h" # include "lineedit/complete.h" # include "lineedit/lineedit.h" #endif /* About the shell's signal handling: * * Yash always catches SIGCHLD. * When job control is active, SIGTTIN, SIGTTOU, and SIGTSTP are ignored. * If the shell is interactive, SIGTERM and SIGQUIT are ignored and SIGINT and * SIGWINCH are caught. * Trapped signals are also caught. * * SIGQUIT and SIGINT are ignored in an asynchronous list. * SIGTSTP is left ignored in command substitution in a job-control shell. * * The shell inherits the signal mask from its invoker and commands invoked by * the shell also inherit it. (POSIX.1-2008) * Signals with the handler installed are almost always blocked to avoid * unexpected interruption of system calls. They are unblocked when: * - the shell waits for input * - the shell waits for a child process to finish * - the shell handles traps. * Also, SIGINT is unblocked when: * - the shell tries to open a socket * - the shell performs pathname expansion. * * SIGTTOU is blocked in `put_foreground' and unblocked in `ensure_foreground'. * All signals are blocked to avoid race conditions when the shell forks. */ static int parse_signal_number(const wchar_t *number) __attribute__((pure)); static void set_special_handler(int signum, void (*handler)(int signum)); static void reset_special_handler( int signum, void (*handler)(int signum), bool leave); static void sig_handler(int signum); static void handle_sigchld(void); static void set_trap(int signum, const wchar_t *command); static bool is_ignored(int signum); static void banish_phantoms(void); #if YASH_ENABLE_LINEEDIT # ifdef SIGWINCH static inline void handle_sigwinch(void); # endif static void sig_new_candidate( const le_compopt_T *restrict compopt, int num, xwcsbuf_T *restrict name) __attribute__((nonnull)); #endif /********** Auxiliary Functions **********/ /* Checks if there exists a process with the specified process ID, which must * be positive. */ bool process_exists(pid_t pid) { assert(pid > 0); return kill(pid, 0) >= 0 || errno != ESRCH; /* send a dummy signal to check if the process exists. */ } /* Returns the name of the signal with the specified number. * The returned name doesn't have the "SIG"-prefix. * An empty string is returned for an unknown signal number. * The returned string is valid until the next call to this function. */ const wchar_t *get_signal_name(int signum) { if (signum == 0) return L"EXIT"; #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN, sigrtmax = SIGRTMAX; if (sigrtmin <= signum && signum <= sigrtmax) { static wchar_t *name = NULL; if (signum == sigrtmin) return L"RTMIN"; if (signum == sigrtmax) return L"RTMAX"; int range = sigrtmax - sigrtmin, diff = signum - sigrtmin; free(name); if (diff <= range / 2) name = malloc_wprintf(L"RTMIN+%d", diff); else name = malloc_wprintf(L"RTMAX-%d", sigrtmax - signum); return name; } #endif for (const signal_T *s = signals; s->no != 0; s++) if (s->no == signum) return s->name; return L""; } /* Returns the number of the signal whose name is `name'. * `name' may have the "SIG"-prefix. * If `name' is an integer that is a valid signal number, the number is * returned. * Returns 0 for "EXIT" and -1 for an unknown name. * `name' should be all in uppercase. */ int get_signal_number(const wchar_t *name) { if (iswdigit(name[0])) return parse_signal_number(name); if (wcscmp(name, L"EXIT") == 0) return 0; if (wcsncmp(name, L"SIG", 3) == 0) name += 3; for (const signal_T *s = signals; s->no != 0; s++) if (wcscmp(name, s->name) == 0) return s->no; #if defined SIGRTMIN && defined SIGRTMAX if (wcsncmp(name, L"RTMIN", 5) == 0) { int sigrtmin = SIGRTMIN; name += 5; if (name[0] == L'\0') { return sigrtmin; } else if (name[0] == L'+') { int num; if (xwcstoi(name, 10, &num)) { assert(num >= 0); if (SIGRTMAX - sigrtmin >= num) return sigrtmin + num; } } } else if (wcsncmp(name, L"RTMAX", 5) == 0) { int sigrtmax = SIGRTMAX; name += 5; if (name[0] == L'\0') { return sigrtmax; } else if (name[0] == L'-') { int num; if (xwcstoi(name, 10, &num)) { assert(num < 0); if (SIGRTMIN - sigrtmax <= num) return sigrtmax + num; } } } #endif return -1; } /* Parses the specified string as non-negative integer and returns the integer * if it is zero or a valid signal number. Returns -1 otherwise. */ int parse_signal_number(const wchar_t *number) { int signum; /* parse `number' */ if (!xwcstoi(number, 10, &signum) || signum < 0) return -1; /* check if `signum' is a valid signal */ if (signum == 0) return 0; #if defined SIGRTMIN && defined SIGRTMAX if (SIGRTMIN <= signum && signum <= SIGRTMAX) return signum; #endif for (const signal_T *s = signals; s->no != 0; s++) if (s->no == signum) return signum; return -1; } /* Returns the number of the signal whose name is `name'. * `name' may have the "SIG"-prefix. * If `name' is an integer that is a valid signal number, the number is * returned. * Returns 0 for "EXIT" and -1 for an unknown name. * The given string is converted into uppercase. */ int get_signal_number_toupper(wchar_t *name) { for (wchar_t *n = name; *n != L'\0'; n++) *n = towupper(*n); return get_signal_number(name); } /********** Signal Handler Management */ /* set to true when any trap other than "ignore" is set */ bool any_trap_set = false; /* flag to indicate a signal is caught. */ static volatile sig_atomic_t any_signal_received = false; /* the signal for which trap is currently executed */ static int handled_signal = -1; /* flags to indicate a signal is caught. */ static volatile sig_atomic_t signal_received[MAXSIGIDX]; /* commands to be executed when a signal is trapped (caught). */ static wchar_t *trap_command[MAXSIGIDX]; /* These arrays are indexed by `sigindex'. The index 0 is for the EXIT trap. */ /* `signal_received' and `trap_command' for real-time signals. */ #if defined SIGRTMIN && defined SIGRTMAX # if RTSIZE == 0 # error "RTSIZE == 0" # endif static volatile sig_atomic_t rtsignal_received[RTSIZE]; static wchar_t *rttrap_command[RTSIZE]; #endif /* If true, the contents of `trap_command` and `rttrap_command` are all * phantom; that is, non-empty trap commands in those arrays should be treated * as "default". Phantoms are used to remember previous trap commands after * they were actually reset to "default". * If false, trap commands are real. */ static bool is_phantom = false; /* The signal mask the shell inherited on invocation. * This mask is inherited by commands the shell invokes. * When a signal's trap is set, the signal is removed from this mask. */ static sigset_t original_sigmask; /* Set of signals whose handler was "ignore" when the shell was invoked but * currently is substituted with the shell's handler. * The handler of these signals must be reset to "ignore" before the shell * invokes another command so that the command inherits "ignore" as the handler. * A signal is added to this set also when its trap handler is set to "ignore". */ static sigset_t ignored_signals; /* Set of signals whose trap is set to other than "ignore". * These signals are almost always blocked. */ static sigset_t trapped_signals; /* Set of signals that are in `original_sigmask' but not in `trapped_signals' */ static sigset_t accept_sigmask; /* This flag is set to true as well as `signal_received[sigindex(SIGCHLD/INT)]' * when SIGCHLD/SIGINT is caught. * This flag is used for job control while `signal_received[...]' is for trap * handling. */ static volatile sig_atomic_t sigchld_received, sigint_received; #if YASH_ENABLE_LINEEDIT && defined(SIGWINCH) /* This flag is set to true as well as `signal_received[sigindex(SIGWINCH)]' * when SIGWINCH is caught. * This flag is used by line-editing. */ static volatile sig_atomic_t sigwinch_received; #endif /* true iff SIGCHLD is handled. */ static bool main_handler_set = false; /* true iff SIGTTIN, SIGTTOU, and SIGTSTP are ignored. */ static bool job_handlers_set = false; /* true iff SIGTERM, SIGINT, SIGQUIT and SIGWINCH are ignored/handled. */ static bool interactive_handlers_set = false; /* Initializes the signal module. */ void init_signal(void) { sigemptyset(&original_sigmask); sigemptyset(&ignored_signals); sigemptyset(&trapped_signals); sigprocmask(SIG_SETMASK, NULL, &original_sigmask); accept_sigmask = original_sigmask; } /* Installs signal handlers used by the shell according to the current settings. */ void set_signals(void) { sigset_t block = trapped_signals; if (!job_handlers_set && doing_job_control_now) { job_handlers_set = true; set_special_handler(SIGTTIN, SIG_IGN); set_special_handler(SIGTTOU, SIG_IGN); set_special_handler(SIGTSTP, SIG_IGN); } if (!interactive_handlers_set && is_interactive_now) { interactive_handlers_set = true; sigaddset(&block, SIGINT); set_special_handler(SIGINT, sig_handler); set_special_handler(SIGTERM, SIG_IGN); set_special_handler(SIGQUIT, SIG_IGN); #if YASH_ENABLE_LINEEDIT && defined(SIGWINCH) sigaddset(&block, SIGWINCH); set_special_handler(SIGWINCH, sig_handler); #endif } if (!main_handler_set) { main_handler_set = true; sigaddset(&block, SIGCHLD); set_special_handler(SIGCHLD, sig_handler); } sigprocmask(SIG_BLOCK, &block, NULL); } /* Restores the original signal handlers for the signals used by the shell. * If `leave' is true, the current process is assumed to be about to exec: * the handler may be left unchanged if the handler is supposed to be reset * during exec. The signal setting for SIGCHLD is restored. * If `leave' is false, the setting for SIGCHLD are not restored. */ void restore_signals(bool leave) { if (job_handlers_set) { job_handlers_set = false; reset_special_handler(SIGTTIN, SIG_IGN, leave); reset_special_handler(SIGTTOU, SIG_IGN, leave); reset_special_handler(SIGTSTP, SIG_IGN, leave); } if (interactive_handlers_set) { interactive_handlers_set = false; reset_special_handler(SIGINT, sig_handler, leave); reset_special_handler(SIGTERM, SIG_IGN, leave); reset_special_handler(SIGQUIT, SIG_IGN, leave); #if YASH_ENABLE_LINEEDIT && defined(SIGWINCH) reset_special_handler(SIGWINCH, sig_handler, leave); #endif } if (main_handler_set) { sigset_t ss = original_sigmask; if (leave) { main_handler_set = false; reset_special_handler(SIGCHLD, sig_handler, leave); } else { sigaddset(&ss, SIGCHLD); } sigprocmask(SIG_SETMASK, &ss, NULL); } } /* Re-sets the signal handler for SIGTTIN, SIGTTOU, and SIGTSTP according to the * current `doing_job_control_now' and `job_handlers_set'. */ void reset_job_signals(void) { if (doing_job_control_now && !job_handlers_set) { job_handlers_set = true; set_special_handler(SIGTTIN, SIG_IGN); set_special_handler(SIGTTOU, SIG_IGN); set_special_handler(SIGTSTP, SIG_IGN); } else if (!doing_job_control_now && job_handlers_set) { job_handlers_set = false; reset_special_handler(SIGTTIN, SIG_IGN, false); reset_special_handler(SIGTTOU, SIG_IGN, false); reset_special_handler(SIGTSTP, SIG_IGN, false); } } /* Sets the signal handler of signal `signum' to function `handler', which must * be either SIG_IGN or `sig_handler'. * If the old handler is SIG_IGN, `signum' is added to `ignored_signals'. * If `handler' is SIG_IGN and the trap for the signal is set, the signal * handler is not changed. */ /* Note that this function does not block or unblock the specified signal. */ void set_special_handler(int signum, void (*handler)(int signum)) { const wchar_t *trap = trap_command[sigindex(signum)]; if (!is_phantom && trap != NULL && trap[0] != L'\0') return; /* The signal handler has already been set. */ struct sigaction action, oldaction; action.sa_flags = 0; action.sa_handler = handler; sigemptyset(&action.sa_mask); sigemptyset(&oldaction.sa_mask); if (sigaction(signum, &action, &oldaction) >= 0) if (oldaction.sa_handler == SIG_IGN) sigaddset(&ignored_signals, signum); } /* Resets the signal handler for signal `signum' to what external commands * should inherit from the shell. The handler that have been passed to * `set_special_handler' must be passed as `handler'. If `leave' is true, the * current process is assumed to be about to exec: the handler may be left * unchanged if the handler is supposed to be reset during exec. */ void reset_special_handler(int signum, void (*handler)(int signum), bool leave) { struct sigaction action; if (sigismember(&ignored_signals, signum)) action.sa_handler = SIG_IGN; else if (sigismember(&trapped_signals, signum)) return; else action.sa_handler = SIG_DFL; if (leave && handler != SIG_IGN) handler = SIG_DFL; if (handler != action.sa_handler) { action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(signum, &action, NULL); } } /* Unblocks SIGINT so that system calls can be interrupted. * First, this function must be called with the argument of true and this * function unblocks SIGINT. Later, this function must be called with the * argument of false and SIGINT is re-blocked. * This function is effective only if the shell is interactive. */ void set_interruptible_by_sigint(bool onoff) { if (interactive_handlers_set) { sigset_t ss; sigemptyset(&ss); sigaddset(&ss, SIGINT); sigprocmask(onoff ? SIG_UNBLOCK : SIG_BLOCK, &ss, NULL); } } /* Sets the signal handler for SIGQUIT and SIGINT to SIG_IGN * to prevent an asynchronous job from being killed by these signals. */ void ignore_sigquit_and_sigint(void) { struct sigaction action; if (!interactive_handlers_set) { sigemptyset(&action.sa_mask); action.sa_flags = 0; action.sa_handler = SIG_IGN; sigaction(SIGQUIT, &action, NULL); sigaction(SIGINT, &action, NULL); } /* Don't set the handers if interactive because they are reset when `restore_signals' is called later. */ sigaddset(&ignored_signals, SIGQUIT); sigaddset(&ignored_signals, SIGINT); } /* Sets the action of SIGTSTP to ignoring the signal * to prevent a command substitution process from being stopped by SIGTSTP. * `doing_job_control_now' must be true. */ void ignore_sigtstp(void) { assert(doing_job_control_now); /* Don't set the hander now because it is reset when `restore_signals' is * called later. struct sigaction action; sigemptyset(&action.sa_mask); action.sa_flags = 0; action.sa_handler = SIG_IGN; sigaction(SIGTSTP, &action, NULL); */ sigaddset(&ignored_signals, SIGTSTP); } /* Sends SIGSTOP to the shell process (and the processes in the same process * group). * On error, an error message is printed to the standard error. */ void stop_myself(void) { if (kill(0, SIGSTOP) < 0) xerror(errno, Ngt("cannot send SIGSTOP signal")); } /* the general signal handler */ void sig_handler(int signum) { any_signal_received = true; #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN; if (sigrtmin <= signum && signum <= SIGRTMAX) { size_t index = signum - sigrtmin; if (index < RTSIZE) rtsignal_received[index] = true; } else #endif { signal_received[sigindex(signum)] = true; switch (signum) { case SIGCHLD: sigchld_received = true; break; case SIGINT: sigint_received = true; break; #if YASH_ENABLE_LINEEDIT && defined(SIGWINCH) case SIGWINCH: sigwinch_received = true; break; #endif } } } /* Accepts currently pending signals and calls `handle_sigchld' and * `handle_traps'. */ void handle_signals(void) { sigset_t ss = accept_sigmask, savess; sigdelset(&ss, SIGCHLD); if (interactive_handlers_set) sigdelset(&ss, SIGINT); sigemptyset(&savess); sigprocmask(SIG_SETMASK, &ss, &savess); sigprocmask(SIG_SETMASK, &savess, NULL); handle_sigchld(); handle_traps(); } /* Waits for SIGCHLD to be caught and call `handle_sigchld'. * If SIGCHLD is already caught, this function doesn't wait. * If `interruptible' is true, this function can be canceled by SIGINT. * If `return_on_trap' is true, this function returns immediately after a trap * is handled. Otherwise, traps are not handled. * Returns the signal number if interrupted or zero if successful. */ int wait_for_sigchld(bool interruptible, bool return_on_trap) { int result = 0; sigset_t ss = accept_sigmask; sigdelset(&ss, SIGCHLD); if (interruptible) sigdelset(&ss, SIGINT); for (;;) { if (return_on_trap && ((result = handle_traps()) != 0)) break; if (interruptible && sigint_received) break; if (sigchld_received) break; if (sigsuspend(&ss) < 0) { if (errno != EINTR) { xerror(errno, "sigsuspend"); break; } } } if (interruptible && sigint_received) result = SIGINT; handle_sigchld(); return result; } /* Waits for the specified file descriptor to be available for reading. * `handle_sigchld' and `handle_sigwinch' are called to handle SIGCHLD and * SIGWINCH that are caught while waiting. * If `trap' is true, traps are also handled while waiting and the * `sigint_received' flag is cleared when this function returns. The return * value can be W_INTERRUPTED only if `trap' is true. Any SIGINT received before * entering this function is ignored. * The maximum time length of wait is specified by `timeout' in milliseconds. * If `timeout' is negative, the wait time is unlimited. * If the wait is interrupted by a signal, this function will re-wait for the * specified timeout, which means that this function may wait for a time length * longer than the specified timeout. */ enum wait_for_input_T wait_for_input(int fd, bool trap, int timeout) { sigset_t ss; struct timespec to; struct timespec *top; assert(fd >= 0); if (fd >= FD_SETSIZE) { xerror(0, Ngt("too many files are opened for yash to handle")); return W_ERROR; } if (trap) sigint_received = false; ss = accept_sigmask; sigdelset(&ss, SIGCHLD); if (interactive_handlers_set) { sigdelset(&ss, SIGINT); #if YASH_ENABLE_LINEEDIT && defined SIGWINCH sigdelset(&ss, SIGWINCH); #endif } if (timeout < 0) { top = NULL; } else { to.tv_sec = timeout / 1000; to.tv_nsec = timeout % 1000 * 1000000; top = &to; } for (;;) { handle_sigchld(); if (trap) handle_traps(); #if YASH_ENABLE_LINEEDIT && defined SIGWINCH handle_sigwinch(); #endif if (trap && sigint_received) { sigint_received = false; return W_INTERRUPTED; } fd_set fdset; FD_ZERO(&fdset); FD_SET(fd, &fdset); int count = pselect(fd + 1, &fdset, NULL, NULL, top, &ss); if (trap && sigint_received) { sigint_received = false; return W_INTERRUPTED; } if (count >= 0) return FD_ISSET(fd, &fdset) ? W_READY : W_TIMED_OUT; if (errno != EINTR) { xerror(errno, "pselect"); return W_ERROR; } } } /* Handles SIGCHLD if caught. */ void handle_sigchld(void) { if (sigchld_received) { sigchld_received = false; do_wait(); } if (any_job_status_has_changed()) { /* print job status if the notify option is set */ #if YASH_ENABLE_LINEEDIT if (le_state & LE_STATE_ACTIVE) { if (!(le_state & LE_STATE_COMPLETING)) { if (shopt_notify || shopt_notifyle) { le_suspend_readline(); print_job_status_all(); le_resume_readline(); } } } else #endif if (shopt_notify) { sigset_t ss, savess; sigemptyset(&ss); sigaddset(&ss, SIGTTOU); sigemptyset(&savess); sigprocmask(SIG_BLOCK, &ss, &savess); print_job_status_all(); sigprocmask(SIG_SETMASK, &savess, NULL); } } } /* Executes the trap handlers for trapped signals if any. * There must not be an active job when this function is called. * Returns the signal number if any handler was executed, otherwise zero. * Note that, if more than one signal is caught, only one of their numbers is * returned. */ int handle_traps(void) { /* Signal handler execution is not reentrant because the value of * `savelaststatus' would be lost. But the EXIT is the only exception: * The EXIT trap may be executed inside another trap. */ if (!any_trap_set || !any_signal_received || handled_signal >= 0) return 0; #if YASH_ENABLE_LINEEDIT /* Don't handle traps during command line completion. Otherwise, the command * line would be messed up! */ if (le_state & LE_STATE_COMPLETING) return 0; #endif int signum = 0; bool save_sigint_received = sigint_received; savelaststatus = laststatus; sigint_received = false; do { /* we reset this before executing signal handlers to avoid race */ any_signal_received = false; for (const signal_T *s = signals; s->no != 0; s++) { size_t i = sigindex(s->no); if (signal_received[i]) { signal_received[i] = false; wchar_t *command = trap_command[i]; if (!is_phantom && command != NULL && command[0] != L'\0') { #if YASH_ENABLE_LINEEDIT le_suspend_readline(); #endif struct execstate_T *execstate = save_execstate(); reset_execstate(true); signum = handled_signal = s->no; command = xwcsdup(command); exec_wcs(command, "trap", false); free(command); cancel_return(); restore_execstate(execstate); laststatus = savelaststatus; } } } #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN, range = SIGRTMAX - sigrtmin + 1; if (range > RTSIZE) range = RTSIZE; for (int i = 0; i < range; i++) { if (rtsignal_received[i]) { rtsignal_received[i] = false; wchar_t *command = rttrap_command[i]; if (!is_phantom && command != NULL && command[0] != L'\0') { #if YASH_ENABLE_LINEEDIT le_suspend_readline(); #endif struct execstate_T *execstate = save_execstate(); reset_execstate(true); signum = handled_signal = sigrtmin + i; command = xwcsdup(command); exec_wcs(command, "trap", false); free(command); cancel_return(); restore_execstate(execstate); laststatus = savelaststatus; } } } #endif /* defined SIGRTMAX && defined SIGRTMIN */ } while (any_signal_received); sigint_received |= save_sigint_received; savelaststatus = -1; handled_signal = -1; #if YASH_ENABLE_LINEEDIT if (shopt_notifyle && (le_state & LE_STATE_SUSPENDED)) print_job_status_all(); le_resume_readline(); #endif return signum; } /* Executes the EXIT trap if any. * This function calls `reset_execstate' without saving `execstate'. */ void execute_exit_trap(void) { if (is_phantom) return; wchar_t *command = trap_command[sigindex(0)]; if (command != NULL) { savelaststatus = laststatus; command = xwcsdup(command); reset_execstate(true); exec_wcs(command, "EXIT trap", false); free(command); savelaststatus = -1; } } /* Sets trap for the signal `signum' to `command'. * If `command' is NULL, the trap is reset to the default. * If `command' is an empty string, the trap is set to SIG_IGN. * This function may call `get_signal_name'. * If `is_phantom` is true, `(rt)trap_command' is not modified. * An error message is printed to the standard error on error. */ void set_trap(int signum, const wchar_t *command) { if (signum == SIGKILL || signum == SIGSTOP) { xerror(0, Ngt("SIG%ls cannot be trapped"), signum == SIGKILL ? L"KILL" : L"STOP"); return; } wchar_t **commandp; volatile sig_atomic_t *receivedp; #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN; if (sigrtmin <= signum && signum <= SIGRTMAX) { size_t index = signum - sigrtmin; if (index < RTSIZE) { commandp = &rttrap_command[index]; receivedp = &rtsignal_received[index]; } else { xerror(0, Ngt("real-time signal SIG%ls is not supported"), get_signal_name(signum)); return; } } else #endif { size_t index = sigindex(signum); commandp = &trap_command[index]; receivedp = &signal_received[index]; } if (!is_interactive && *commandp == NULL && is_ignored(signum)) { /* Signals that were ignored on entry to a non-interactive shell cannot * be trapped or reset. (POSIX) */ #if FIXED_SIGNAL_AS_ERROR xerror(0, Ngt("SIG%ls cannot be reset"), get_signal_name(signum)); #endif return; } if (!is_phantom) { free(*commandp); if (command != NULL) { if (command[0] != L'\0') any_trap_set = true; *commandp = xwcsdup(command); } else { *commandp = NULL; } *receivedp = false; } if (signum == 0) return; struct sigaction action; if (command == NULL) action.sa_handler = SIG_DFL; else if (command[0] == L'\0') action.sa_handler = SIG_IGN; else action.sa_handler = sig_handler; if (action.sa_handler == SIG_IGN) { sigaddset(&ignored_signals, signum); } else { sigdelset(&ignored_signals, signum); } if (action.sa_handler == sig_handler) { sigdelset(&original_sigmask, signum); sigaddset(&trapped_signals, signum); sigdelset(&accept_sigmask, signum); } else { sigdelset(&trapped_signals, signum); } switch (signum) { case SIGCHLD: /* SIGCHLD's signal handler is always `sig_handler' */ return; case SIGINT: #if YASH_ENABLE_LINEEDIT && defined SIGWINCH case SIGWINCH: #endif /* SIGINT and SIGWINCH's signal handler is always `sig_handler' * when interactive */ if (interactive_handlers_set) return; break; case SIGTTIN: case SIGTTOU: case SIGTSTP: if (job_handlers_set) goto default_ignore; break; case SIGTERM: case SIGQUIT: if (interactive_handlers_set) goto default_ignore; break; default_ignore: if (action.sa_handler == SIG_DFL) action.sa_handler = SIG_IGN; break; } if (action.sa_handler == sig_handler) sigprocmask(SIG_BLOCK, &trapped_signals, NULL); sigemptyset(&action.sa_mask); action.sa_flags = 0; if (sigaction(signum, &action, NULL) < 0) { int saveerrno = errno; xerror(saveerrno, "sigaction(SIG%ls)", get_signal_name(signum)); } } /* Checks if the specified signal is ignored. * Asserts the shell is not interactive. */ bool is_ignored(int signum) { assert(!is_interactive_now); if (signum == 0) return false; if (doing_job_control_now && (signum == SIGTTIN || signum == SIGTTOU || signum == SIGTSTP)) return sigismember(&ignored_signals, signum); struct sigaction action; sigemptyset(&action.sa_mask); return sigaction(signum, NULL, &action) >= 0 && action.sa_handler == SIG_IGN; } /* Clears the EXIT trap. */ void clear_exit_trap(void) { set_trap(0, NULL); } /* Change all traps into phantom except that are set to SIG_IGN. */ void phantomize_traps(void) { if (!any_trap_set && !any_signal_received) return; is_phantom = true; { size_t index = sigindex(0); wchar_t *command = trap_command[index]; if (command != NULL && command[0] != L'\0') set_trap(0, NULL); signal_received[index] = false; } for (const signal_T *s = signals; s->no != 0; s++) { size_t index = sigindex(s->no); wchar_t *command = trap_command[index]; if (command != NULL && command[0] != L'\0') set_trap(s->no, NULL); signal_received[index] = false; } #if defined SIGRTMIN && defined SIGRTMAX for (int sigrtmin = SIGRTMIN, i = 0; i < RTSIZE; i++) { wchar_t *command = rttrap_command[i]; if (command != NULL && command[0] != L'\0') set_trap(sigrtmin + i, NULL); rtsignal_received[i] = false; } #endif any_trap_set = false, any_signal_received = false; } /* Remove all phantoms from `trap_command` and `rttrap_command`. */ void banish_phantoms(void) { if (!is_phantom) return; is_phantom = false; { size_t index = sigindex(0); wchar_t *command = trap_command[index]; if (command != NULL && command[0] != L'\0') { free(command); trap_command[index] = NULL; } } for (const signal_T *s = signals; s->no != 0; s++) { size_t index = sigindex(s->no); wchar_t *command = trap_command[index]; if (command != NULL && command[0] != L'\0') { free(command); trap_command[index] = NULL; } } #if defined SIGRTMIN && defined SIGRTMAX for (int i = 0; i < RTSIZE; i++) { wchar_t *command = rttrap_command[i]; if (command != NULL && command[0] != L'\0') { free(command); rttrap_command[i] = NULL; } } #endif } /* Tests the `sigint_received' flag. Returns true only if interactive. */ bool is_interrupted(void) { return is_interactive_now && sigint_received; } /* Sets `laststatus' to (SIGINT + TERMSIGOFFSET) if the shell has been * interrupted. */ void set_laststatus_if_interrupted(void) { if (is_interrupted()) laststatus = SIGINT + TERMSIGOFFSET; } /* Sets the `sigint_received' flag. */ void set_interrupted(void) { sigint_received = true; } #if YASH_ENABLE_LINEEDIT #ifdef SIGWINCH /* If SIGWINCH has been caught and line-editing is currently active, cause * line-editing to redraw the display. */ void handle_sigwinch(void) { if (sigwinch_received) le_display_size_changed(); } #endif /* defined SIGWINCH */ /* Resets the `sigwinch_received' flag. */ void reset_sigwinch(void) { #ifdef SIGWINCH sigwinch_received = false; #endif } /* Generates completion candidates for signal names matching the pattern. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_signal_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_SIGNAL)) return; le_compdebug("adding signal name candidates"); if (!le_compile_cpatterns(compopt)) return; bool prefix = matchwcsprefix(compopt->src, L"SIG"); xwcsbuf_T buf; wb_init(&buf); for (const signal_T *s = signals; s->no != 0; s++) { if (prefix) wb_cat(&buf, L"SIG"); wb_cat(&buf, s->name); sig_new_candidate(compopt, s->no, &buf); } #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN, sigrtmax = SIGRTMAX; for (int s = sigrtmin; s <= sigrtmax; s++) { if (prefix) wb_cat(&buf, L"SIG"); wb_cat(&buf, L"RTMIN"); if (s != sigrtmin) wb_wprintf(&buf, L"+%d", s - sigrtmin); sig_new_candidate(compopt, s, &buf); if (prefix) wb_cat(&buf, L"SIG"); wb_cat(&buf, L"RTMAX"); if (s != sigrtmax) wb_wprintf(&buf, L"-%d", sigrtmax - s); sig_new_candidate(compopt, s, &buf); } #endif wb_destroy(&buf); } /* If the pattern in `compopt' matches the specified signal name, adds a new * completion candidate with the name and the description for the signal. */ void sig_new_candidate( const le_compopt_T *restrict compopt, int num, xwcsbuf_T *restrict name) { if (le_wmatch_comppatterns(compopt, name->contents)) { xwcsbuf_T desc; wb_init(&desc); #if HAVE_STRSIGNAL char *mbsdesc = strsignal(num); if (mbsdesc != NULL) wb_wprintf(&desc, L"%d: %s", num, mbsdesc); else #endif wb_wprintf(&desc, L"%d", num); le_new_candidate(CT_SIG, wb_towcs(name), wb_towcs(&desc), compopt); wb_init(name); } else { wb_clear(name); } } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-in **********/ static bool print_trap(const wchar_t *signame, const wchar_t *command) __attribute__((nonnull(1))); static bool print_signal(int signum, const wchar_t *name, bool verbose) __attribute__((nonnull)); static void signal_job(int signum, const wchar_t *jobname) __attribute__((nonnull)); /* Options for the "trap" built-in. */ const struct xgetopt_T trap_options[] = { { L'p', L"print", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "trap" built-in. */ int trap_builtin(int argc, void **argv) { bool print = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, trap_options, 0)) != NULL) { switch (opt->shortopt) { case L'p': print = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (xoptind == argc) { /* print all traps */ sigset_t printed; sigemptyset(&printed); if (!print_trap(L"EXIT", trap_command[sigindex(0)])) return Exit_FAILURE; for (const signal_T *s = signals; s->no != 0; s++) { if (!sigismember(&printed, s->no)) { sigaddset(&printed, s->no); if (!print_trap(s->name, trap_command[sigindex(s->no)])) return special_builtin_error(Exit_FAILURE); } } #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN, sigrtmax = SIGRTMAX; for (int i = 0; i < RTSIZE; i++) { if (sigrtmin + i > sigrtmax) break; if (!print_trap(get_signal_name(sigrtmin + i), rttrap_command[i])) return special_builtin_error(Exit_FAILURE); } #endif } else if (print) { /* print specified traps */ #if defined SIGRTMIN && defined SIGRTMAX int sigrtmin = SIGRTMIN, sigrtmax = SIGRTMAX; #endif do { wchar_t *name = ARGV(xoptind); int signum = get_signal_number_toupper(name); if (signum < 0) { xerror(0, Ngt("no such signal `%ls'"), name); continue; } #if defined SIGRTMIN && defined SIGRTMAX if (sigrtmin <= signum && signum <= sigrtmax) { int index = signum - sigrtmin; if (index < RTSIZE) if (!print_trap(name, rttrap_command[index])) return special_builtin_error(Exit_FAILURE); } else #endif { if (!print_trap(name, trap_command[sigindex(signum)])) return special_builtin_error(Exit_FAILURE); } } while (++xoptind < argc); } else { const wchar_t *command; /* check if the first operand is an integer */ wchar_t *end; errno = 0; if (iswdigit(ARGV(xoptind)[0]) && (wcstoul(ARGV(xoptind), &end, 10), *end == L'\0')) { command = NULL; } else { command = ARGV(xoptind++); if (xoptind == argc) return special_builtin_error(insufficient_operands_error(2)); if (wcscmp(command, L"-") == 0) command = NULL; } /* set traps */ banish_phantoms(); do { wchar_t *name = ARGV(xoptind); int signum = get_signal_number_toupper(name); if (signum < 0) { xerror(0, Ngt("no such signal `%ls'"), name); continue; } set_trap(signum, command); } while (++xoptind < argc); } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Prints trap to the standard output in a format that can be used to restore * the current signal handler for the specified signal. * If the `command' is NULL, this function does nothing. * Otherwise, the `command' is properly single-quoted and printed. * Returns true iff successful (no error). On error, an error message is printed * to the standard error. */ bool print_trap(const wchar_t *signame, const wchar_t *command) { if (command == NULL) return true; wchar_t *q = quote_as_word(command); bool ok = xprintf("trap -- %ls %ls\n", q, signame); free(q); return ok; } #if YASH_ENABLE_HELP const char trap_help[] = Ngt( "set or print signal handlers" ); const char trap_syntax[] = Ngt( "\ttrap [action signal...]\n" "\ttrap signal_number [signal...]\n" "\ttrap -p [signal...]\n" ); #endif /* The "kill" built-in, which accepts the following options: * -s sig: specifies the signal to send * -n num: specifies the signal to send by number * -l: prints signal info * -v: prints signal info verbosely */ int kill_builtin(int argc, void **argv) { int signum = SIGTERM; bool list = false, verbose = false; /* We don't use the xgetopt function to parse options because the kill * built-in has non-standard syntax. */ int optind; for (optind = 1; optind < argc; optind++) { wchar_t *arg = ARGV(optind); if (arg[0] != L'-' || arg[1] == L'\0') break; for (size_t i = 1; arg[i] != L'\0'; i++) { switch (arg[i]) { case L'n': case L's': /* we don't make any differences between -n and -s options */ if (list) return mutually_exclusive_option_error(arg[i], L'l'); arg = &arg[i + 1]; if (arg[0] == L'\0') { arg = ARGV(++optind); if (arg == NULL) { xerror(0, Ngt("the signal name is not specified")); return Exit_ERROR; } } parse_signal_name: if (posixly_correct && matchwcsprefix(arg, L"SIG")) { xerror(0, Ngt("%ls: the signal name must be specified " "without `SIG'"), arg); return Exit_ERROR; } signum = get_signal_number_toupper(arg); if (signum < 0 || (signum == 0 && !iswdigit(arg[0]))) { xerror(0, Ngt("no such signal `%ls'"), arg); return Exit_FAILURE; } optind++; if (optind < argc && wcscmp(ARGV(optind), L"--") == 0) optind++; goto main; case L'l': list = true; break; case L'v': list = verbose = true; break; case L'-': if (i == 1) { if (arg[2] == L'\0') { /* `arg' is "--" */ optind++; goto main; } #if YASH_ENABLE_HELP if (!posixly_correct && matchwcsprefix(L"--help", arg)) return print_builtin_help(ARGV(0)); #endif } /* falls thru! */ default: if (i == 1 && !list) { arg = &arg[i]; goto parse_signal_name; } else { xerror(0, Ngt("`%ls' is not a valid option"), arg); return Exit_ERROR; } } } } main: if (list) { if (optind == argc) { /* print info of all signals */ for (const signal_T *s = signals; s->no != 0; s++) if (!print_signal(s->no, s->name, verbose)) return Exit_FAILURE; #if defined SIGRTMIN && defined SIGRTMAX for (int i = SIGRTMIN, max = SIGRTMAX; i <= max; i++) if (!print_signal(i, get_signal_name(i), verbose)) return Exit_FAILURE; #endif } else { /* print info of the specified signals */ do { int signum; const wchar_t *signame; if (xwcstoi(ARGV(optind), 10, &signum) && signum >= 0) { if (signum >= TERMSIGOFFSET) signum -= TERMSIGOFFSET; else if (signum >= (TERMSIGOFFSET & 0xFF)) signum -= (TERMSIGOFFSET & 0xFF); } else { signum = get_signal_number_toupper(ARGV(optind)); } signame = get_signal_name(signum); if (signum <= 0 || signame[0] == L'\0') { xerror(0, Ngt("no such signal `%ls'"), ARGV(optind)); continue; } if (!print_signal(signum, signame, verbose)) return Exit_FAILURE; } while (++optind < argc); } } else { /* send signal */ if (optind == argc) return insufficient_operands_error(1); do { wchar_t *proc = ARGV(optind); if (proc[0] == L'%') { signal_job(signum, proc); } else { long pid; if (!xwcstol(proc, 10, &pid)) { xerror(0, Ngt("`%ls' is not a valid integer"), proc); continue; } // XXX this cast might not be safe if (kill((pid_t) pid, signum) < 0) { xerror(errno, "%ls", proc); continue; } } } while (++optind < argc); } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Prints info about the specified signal. * `signum' must be a valid signal number and `name' must be the name of the * signal. * Returns false iff an IO error occurred. */ bool print_signal(int signum, const wchar_t *name, bool verbose) { if (!verbose) { return xprintf("%ls\n", name); } else { #if HAVE_STRSIGNAL const char *sigdesc = strsignal(signum); if (sigdesc != NULL) return xprintf("%d\t%-10ls %s\n", signum, name, sigdesc); else #endif return xprintf("%d\t%-10ls\n", signum, name); } } /* Sends the specified signal to the specified job. * Returns true iff successful. On error, an error message is printed to the * standard error. */ void signal_job(int signum, const wchar_t *jobspec) { pid_t jobpgid = get_job_pgid(jobspec); if (jobpgid <= 0) return; if (kill(-jobpgid, signum) < 0) xerror(errno, "%ls", jobspec); } #if YASH_ENABLE_HELP const char kill_help[] = Ngt( "send a signal to processes" ); const char kill_syntax[] = Ngt( "\tkill [-signal|-s signal|-n number] process...\n" "\tkill -l [-v] [number...]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/sig.h000066400000000000000000000050031354143602500133500ustar00rootroot00000000000000/* Yash: yet another shell */ /* sig.h: signal handling */ /* (C) 2007-2016 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_SIG_H #define YASH_SIG_H #include #include #include "xgetopt.h" extern _Bool process_exists(pid_t pid); extern const wchar_t *get_signal_name(int signum) __attribute__((const)); extern int get_signal_number(const wchar_t *name) __attribute__((nonnull,pure)); extern int get_signal_number_toupper(wchar_t *name) __attribute__((nonnull)); extern _Bool any_trap_set; extern void init_signal(void); extern void set_signals(void); extern void restore_signals(_Bool leave); extern void reset_job_signals(void); extern void set_interruptible_by_sigint(_Bool onoff); extern void ignore_sigquit_and_sigint(void); extern void ignore_sigtstp(void); extern void stop_myself(void); extern void handle_signals(void); extern int wait_for_sigchld(_Bool interruptible, _Bool return_on_trap); enum wait_for_input_T { W_READY, W_TIMED_OUT, W_INTERRUPTED, W_ERROR, }; extern enum wait_for_input_T wait_for_input(int fd, _Bool trap, int timeout); extern int handle_traps(void); extern void execute_exit_trap(void); extern void clear_exit_trap(void); extern void phantomize_traps(void); extern _Bool is_interrupted(void); extern void set_laststatus_if_interrupted(void); extern void set_interrupted(void); #if YASH_ENABLE_LINEEDIT extern void reset_sigwinch(void); #endif extern int trap_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char trap_help[], trap_syntax[]; #endif extern const struct xgetopt_T trap_options[]; extern int kill_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char kill_help[], kill_syntax[]; #endif #if HAVE_STRSIGNAL && !defined(strsignal) extern char *strsignal(int signum); #endif #endif /* YASH_SIG_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/siglist.h000066400000000000000000000073071354143602500142550ustar00rootroot00000000000000/* Yash: yet another shell */ /* siglist.h: defines list of signals */ /* (C) 2007-2009 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_SIGLIST_H #define YASH_SIGLIST_H #include #include /* signal number and name */ typedef struct signal_T { int no; const wchar_t *name; } signal_T; /* list of signals */ static const signal_T signals[] = { /* signals defined by SUSv2 & POSIX.1-2001 (SUSv3) */ { SIGHUP, L"HUP", }, { SIGINT, L"INT", }, { SIGQUIT, L"QUIT", }, { SIGILL, L"ILL", }, { SIGABRT, L"ABRT", }, { SIGBUS, L"BUS", }, { SIGFPE, L"FPE", }, { SIGKILL, L"KILL", }, { SIGSEGV, L"SEGV", }, { SIGPIPE, L"PIPE", }, { SIGALRM, L"ALRM", }, { SIGTERM, L"TERM", }, { SIGUSR1, L"USR1", }, { SIGUSR2, L"USR2", }, { SIGCHLD, L"CHLD", }, { SIGCONT, L"CONT", }, { SIGSTOP, L"STOP", }, { SIGTSTP, L"TSTP", }, { SIGTTIN, L"TTIN", }, { SIGTTOU, L"TTOU", }, { SIGURG, L"URG", }, #ifdef SIGTRAP { SIGTRAP, L"TRAP", }, #endif #ifdef SIGXCPU { SIGXCPU, L"XCPU", }, #endif #ifdef SIGXFSZ { SIGXFSZ, L"XFSZ", }, #endif #ifdef SIGVTALRM { SIGVTALRM, L"VTALRM", }, #endif #ifdef SIGPROF { SIGPROF, L"PROF", }, #endif #ifdef SIGPOLL { SIGPOLL, L"POLL", }, #endif #ifdef SIGSYS { SIGSYS, L"SYS", }, #endif /* below are non-standardized signals */ #ifdef SIGIOT { SIGIOT, L"IOT", }, #endif #ifdef SIGEMT { SIGEMT, L"EMT", }, #endif #ifdef SIGSTKFLT { SIGSTKFLT, L"STKFLT", }, #endif #ifdef SIGIO { SIGIO, L"IO", }, #endif #ifdef SIGCLD { SIGCLD, L"CLD", }, #endif #ifdef SIGPWR { SIGPWR, L"PWR", }, #endif #ifdef SIGLOST { SIGLOST, L"LOST", }, #endif #ifdef SIGWINCH { SIGWINCH, L"WINCH", }, #endif #ifdef SIGWINDOW { SIGWINDOW, L"WINDOW", }, #endif /* from BSD */ #ifdef SIGINFO { SIGINFO, L"INFO", }, #endif #ifdef SIGTHR { SIGTHR, L"THR", }, #endif /* from AIX */ #ifdef SIGMSG { SIGMSG, L"MSG", }, #endif #ifdef SIGDANGER { SIGDANGER, L"DANGER", }, #endif #ifdef SIGMIGRATE { SIGMIGRATE, L"MIGRATE", }, #endif #ifdef SIGPRE { SIGPRE, L"PRE", }, #endif #ifdef SIGVIRT { SIGVIRT, L"VIRT", }, #endif #ifdef SIGALRM1 { SIGALRM1, L"ALRM1", }, #endif #ifdef SIGWAITING { SIGWAITING, L"WAITING", }, #endif #ifdef SIGKAP { SIGKAP, L"KAP", }, #endif #ifdef SIGGRANT { SIGGRANT, L"GRANT", }, #endif #ifdef SIGRETRACT { SIGRETRACT, L"RETRACT", }, #endif #ifdef SIGSOUND { SIGSOUND, L"SOUND", }, #endif #ifdef SIGSAK { SIGSAK, L"SAK", }, #endif /* from SunOS5 */ #ifdef SIGLWP { SIGLWP, L"LWP", }, #endif #ifdef SIGFREEZE { SIGFREEZE, L"FREEZE", }, #endif #ifdef SIGTHAW { SIGTHAW, L"THAW", }, #endif #ifdef SIGCANCEL { SIGCANCEL, L"CANCEL", }, #endif #ifdef SIGXRES { SIGXRES, L"XRES", }, #endif /* from HP-UX */ #ifdef SIGRESERVE { SIGRESERVE, L"RESERVE", }, #endif #ifdef SIGDIL { SIGDIL, L"DIL", }, #endif #ifdef SIGUNUSED { SIGUNUSED, L"UNUSED", }, #endif /* end of array: any signal number is non-zero (C99 7.14) */ { 0, NULL, }, }; #endif /* YASH_SIGLIST_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/strbuf.c000066400000000000000000000473311354143602500141000ustar00rootroot00000000000000/* Yash: yet another shell */ /* strbuf.c: modifiable string buffer */ /* (C) 2007-2015 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "strbuf.h" #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #if HAVE_WCSNRTOMBS && !defined(wcsnrtombs) size_t wcsnrtombs(char *restrict dst, const wchar_t **restrict src, size_t nwc, size_t len, mbstate_t *restrict ps); #endif #ifndef XSTRBUF_INITSIZE #define XSTRBUF_INITSIZE 15 #endif #ifndef XWCSBUF_INITSIZE #define XWCSBUF_INITSIZE 15 #endif /* If the type of the return value of the functions below is string buffer, * the return value is the argument buffer. */ /********** Multibyte String Buffer **********/ /* Initializes the specified string buffer as an empty string. */ xstrbuf_T *sb_init(xstrbuf_T *buf) { // buf->contents = xmallocn(XSTRBUF_INITSIZE + 1, sizeof (char)); buf->contents = xmalloc(XSTRBUF_INITSIZE + 1); buf->contents[0] = '\0'; buf->length = 0; buf->maxlength = XSTRBUF_INITSIZE; return buf; } /* Initializes the specified multibyte string buffer with the specified string. * String `s' must be `free'able. * After calling this function, the string is used as the buffer, so you must * not touch or `free' it any more. */ xstrbuf_T *sb_initwith(xstrbuf_T *restrict buf, char *restrict s) { buf->contents = s; buf->length = buf->maxlength = strlen(s); return buf; } /* Changes the maximum length of the specified buffer. * If `newmax' is less than the current length of the buffer, the end of * the buffer contents is truncated. */ xstrbuf_T *sb_setmax(xstrbuf_T *buf, size_t newmax) { // buf->contents = xrealloce(buf->contents, newmax, 1, sizeof (char)); buf->contents = xrealloc(buf->contents, add(newmax, 1)); buf->maxlength = newmax; buf->contents[newmax] = '\0'; if (newmax < buf->length) buf->length = newmax; return buf; } /* If `buf->maxlength' is less than `max', reallocates the buffer so that * `buf->maxlength' is no less than `max'. */ xstrbuf_T *sb_ensuremax(xstrbuf_T *buf, size_t max) { if (max <= buf->maxlength) return buf; size_t len15 = buf->maxlength + (buf->maxlength >> 1); if (max < len15) max = len15; if (max < buf->maxlength + 10) max = buf->maxlength + 10; return sb_setmax(buf, max); } /* Replaces the specified part of the buffer with another string. * `bn' characters starting at offset `i' in buffer `buf' is removed and * the first `sn' characters of `s' take place of them. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_replace_force( xstrbuf_T *restrict buf, size_t i, size_t bn, const char *restrict s, size_t sn) { size_t newlength = add(buf->length - bn, sn); sb_ensuremax(buf, newlength); memmove(buf->contents + i + sn, buf->contents + i + bn, buf->length - (i + bn) + 1); memcpy(buf->contents + i, s, sn); buf->length = newlength; return buf; } /* Replaces the specified part of the buffer with another string. * `bn' characters starting at offset `i' in buffer `buf' is removed and * the first `sn' characters of `s' take place of them. * If (strlen(s) < sn), the whole of `s' is replaced with. * If (buf->length < i + sn), all the characters after offset `i' in the buffer * is replaced. Especially, if (buf->length <= i), `s' is appended. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_replace( xstrbuf_T *restrict buf, size_t i, size_t bn, const char *restrict s, size_t sn) { sn = xstrnlen(s, sn); if (i > buf->length) i = buf->length; if (bn > buf->length - i) bn = buf->length - i; return sb_replace_force(buf, i, bn, s, sn); } /* Appends byte `c' to the end of string buffer `buf'. * The byte is appended even if it is a null byte. */ xstrbuf_T *sb_ccat(xstrbuf_T *buf, char c) { sb_ensuremax(buf, add(buf->length, 1)); buf->contents[buf->length++] = c; buf->contents[buf->length] = '\0'; return buf; } /* Appends `n' bytes of `c' to the end of buffer `buf'. * The bytes are appended even if `c' is a null byte. */ xstrbuf_T *sb_ccat_repeat(xstrbuf_T *buf, char c, size_t n) { sb_ensuremax(buf, add(buf->length, n)); memset(&buf->contents[buf->length], c, n); buf->length += n; buf->contents[buf->length] = '\0'; return buf; } /* Converts wide character `c' into a multibyte string and appends it to buffer * `buf'. Shift state `ps' is used for the conversion. * If `c' is a null character, the shift state is reset to the initial state but * the null character is not appended to the buffer. * Returns true iff successful. On error, `errno' is set to EILSEQ and the state * is left undefined. */ bool sb_wccat(xstrbuf_T *restrict buf, wchar_t c, mbstate_t *restrict ps) { size_t count; sb_ensuremax(buf, add(buf->length, MB_CUR_MAX)); count = wcrtomb(&buf->contents[buf->length], c, ps); if (count == (size_t) -1) { buf->contents[buf->length] = '\0'; return false; } assert(0 < count && count <= buf->maxlength - buf->length); buf->length += count; if (c == L'\0') buf->length--; else buf->contents[buf->length] = '\0'; assert(buf->contents[buf->length] == '\0'); return true; } /* Appends first `n' characters of wide string `s' to multibyte buffer `buf'. * The wide string is converted to multibyte string using shift state `ps'. * If `n' is larger than the length of `s', the whole string is appended and * the shift state is reset to the initial shift state. * Returns NULL if the string is converted and appended successfully, * otherwise a pointer to the character in `s' that caused the error. * A partial result may be left in the buffer on error. */ wchar_t *sb_wcsncat(xstrbuf_T *restrict buf, const wchar_t *restrict s, size_t n, mbstate_t *restrict ps) { #if HAVE_WCSNRTOMBS for (;;) { const wchar_t *saves = s; size_t count = wcsnrtombs(&buf->contents[buf->length], (const wchar_t **) &s, n, buf->maxlength - buf->length, ps); if (count == (size_t) -1) { buf->contents[buf->length] = '\0'; break; } buf->length += count; if (s == NULL) break; assert((size_t) (s - saves) <= n); n -= s - saves; if (n == 0) { buf->contents[buf->length] = '\0'; s = NULL; break; } sb_ensuremax(buf, add(buf->maxlength, MB_CUR_MAX)); } assert(buf->contents[buf->length] == '\0'); return (wchar_t *) s; #else while (n > 0) { if (!sb_wccat(buf, *s, ps)) return (wchar_t *) s; if (*s == L'\0') return NULL; s++, n--; } return NULL; #endif } /* Appends wide string `s' to multibyte buffer `buf'. The wide string is * converted to multibyte string using shift state `ps'. After successful * conversion, the shift state is reset to the initial shift state. * Returns NULL if the whole string is converted and appended successfully, * otherwise a pointer to the character in `s' that caused the error. * A partial result may be left in the buffer on error. */ #if !HAVE_WCSNRTOMBS wchar_t *sb_wcscat(xstrbuf_T *restrict buf, const wchar_t *restrict s, mbstate_t *restrict ps) { for (;;) { size_t count = wcsrtombs(&buf->contents[buf->length], (const wchar_t **) &s, buf->maxlength - buf->length, ps); if (count == (size_t) -1) { buf->contents[buf->length] = '\0'; break; } buf->length += count; if (s == NULL) break; sb_ensuremax(buf, add(buf->maxlength, MB_CUR_MAX)); } assert(buf->contents[buf->length] == '\0'); return (wchar_t *) s; } #endif /* Appends the result of `vsprintf' to the specified buffer. * `format' and the following arguments must not be part of `buf->contents'. * Returns the number of appended bytes if successful. * On error, the buffer is not changed and -1 is returned. */ int sb_vprintf(xstrbuf_T *restrict buf, const char *restrict format, va_list ap) { va_list copyap; va_copy(copyap, ap); // No need to check for overflow in `buf->maxlength - buf->length + 1' here. // Should overflow occur, the buffer would not have been allocated // successfully. size_t rest = buf->maxlength - buf->length + 1; int result = vsnprintf(&buf->contents[buf->length], rest, format, ap); // If the buffer was too small... #if INT_MAX < SIZE_MAX if (result >= 0 && (size_t) result >= rest) { #else // INT_MAX >= SIZE_MAX if (result >= (int) rest) { if (result > (int) SIZE_MAX) alloc_failed(); #endif // retry with a larger buffer sb_ensuremax(buf, add(buf->length, (size_t) result)); rest = buf->maxlength - buf->length + 1; result = vsnprintf(&buf->contents[buf->length], rest, format, copyap); } #if INT_MAX < SIZE_MAX assert(result < 0 || (size_t) result < rest); #else // INT_MAX >= SIZE_MAX assert(result < (int) rest); #endif if (result >= 0) buf->length += result; else buf->contents[buf->length] = '\0'; assert(buf->contents[buf->length] == '\0'); va_end(copyap); return result; } /* Appends the result of `sprintf' to the specified buffer. * `format' and the following arguments must not be part of `buf->contents'. * Returns the number of appended bytes if successful. * On error, the buffer is not changed and -1 is returned. */ int sb_printf(xstrbuf_T *restrict buf, const char *restrict format, ...) { va_list ap; int result; va_start(ap, format); result = sb_vprintf(buf, format, ap); va_end(ap); return result; } /********** Wide String Buffer **********/ /* Initializes the specified wide string buffer as an empty string. */ xwcsbuf_T *wb_init(xwcsbuf_T *buf) { buf->contents = xmallocn(XWCSBUF_INITSIZE + 1, sizeof (wchar_t)); buf->contents[0] = L'\0'; buf->length = 0; buf->maxlength = XWCSBUF_INITSIZE; return buf; } /* Initializes the specified wide string buffer with the specified string. * String `s' must be `free'able. * After calling this function, the string is used as the buffer, so you must * not touch or `free' it any more. */ xwcsbuf_T *wb_initwith(xwcsbuf_T *restrict buf, wchar_t *restrict s) { buf->contents = s; buf->length = buf->maxlength = wcslen(s); return buf; } /* Changes the maximum length of the specified buffer. * If `newmax' is less than the current length of the buffer, the end of * the buffer contents is truncated. */ xwcsbuf_T *wb_setmax(xwcsbuf_T *buf, size_t newmax) { buf->contents = xrealloce(buf->contents, newmax, 1, sizeof (wchar_t)); buf->maxlength = newmax; buf->contents[newmax] = L'\0'; if (newmax < buf->length) buf->length = newmax; return buf; } /* If `buf->maxlength' is less than `max', reallocates the buffer so that * `buf->maxlength' is no less than `max'. */ xwcsbuf_T *wb_ensuremax(xwcsbuf_T *buf, size_t max) { if (max <= buf->maxlength) return buf; size_t len15 = buf->maxlength + (buf->maxlength >> 1); if (max < len15) max = len15; if (max < buf->maxlength + 8) max = buf->maxlength + 8; return wb_setmax(buf, max); } /* Replaces the specified part of the buffer with another string. * `bn' characters starting at offset `i' in buffer `buf' is removed and * the first `sn' characters of `s' take place of them. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_replace_force( xwcsbuf_T *restrict buf, size_t i, size_t bn, const wchar_t *restrict s, size_t sn) { size_t newlength = add(buf->length - bn, sn); wb_ensuremax(buf, newlength); wmemmove(buf->contents + i + sn, buf->contents + i + bn, buf->length - (i + bn) + 1); wmemcpy(buf->contents + i, s, sn); buf->length = newlength; return buf; } /* Replaces the specified part of the buffer with another string. * `bn' characters starting at offset `i' in buffer `buf' is removed and * the first `sn' characters of `s' take place of them. * If (wcslen(s) < sn), the whole of `s' is replaced with. * If (buf->length < i + sn), all the characters after offset `i' in the buffer * is replaced. Especially, if (buf->length <= i), `s' is appended. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_replace( xwcsbuf_T *restrict buf, size_t i, size_t bn, const wchar_t *restrict s, size_t sn) { sn = xwcsnlen(s, sn); if (i > buf->length) i = buf->length; if (bn > buf->length - i) bn = buf->length - i; return wb_replace_force(buf, i, bn, s, sn); } /* Appends wide character `c' to the end of buffer `buf'. * The character is appended even if it is a null wide character. */ xwcsbuf_T *wb_wccat(xwcsbuf_T *buf, wchar_t c) { wb_ensuremax(buf, add(buf->length, 1)); buf->contents[buf->length++] = c; buf->contents[buf->length] = L'\0'; return buf; } /* Converts multibyte string `s' into a wide string and appends it to buffer * `buf'. The multibyte string is assumed to start in the initial shift state. * Returns NULL if the whole string is converted and appended successfully, * otherwise a pointer to the character in `s' that caused the error. * A partial result may be left in the buffer on error. */ char *wb_mbscat(xwcsbuf_T *restrict buf, const char *restrict s) { mbstate_t state; size_t count; memset(&state, 0, sizeof state); // initialize as the initial shift state for (;;) { count = mbsrtowcs(&buf->contents[buf->length], (const char **) &s, buf->maxlength - buf->length + 1, &state); if (count == (size_t) -1) break; buf->length += count; if (s == NULL) break; wb_ensuremax(buf, add(buf->maxlength, 1)); } buf->contents[buf->length] = L'\0'; return (char *) s; } /* Appends the result of `vswprintf' to the specified buffer. * `format' and the following arguments must not be part of `buf->contents'. * Returns the number of appended characters if successful. * On error, the buffer is not changed and -1 is returned. */ int wb_vwprintf( xwcsbuf_T *restrict buf, const wchar_t *restrict format, va_list ap) { va_list copyap; size_t rest; int result; for (int i = 0; i < 20; i++) { va_copy(copyap, ap); rest = buf->maxlength - buf->length + 1; result = vswprintf(&buf->contents[buf->length], rest, format, copyap); va_end(copyap); if (0 <= result && #if INT_MAX < SIZE_MAX (size_t) result < rest) #else // INT_MAX >= SIZE_MAX result < (int) rest) #endif break; #if INT_MAX > SIZE_MAX if (result > (int) SIZE_MAX) alloc_failed(); #endif /* According to POSIX, if the buffer is too short, `vswprintf' returns * a negative integer. On some systems, however, it returns a desired * buffer length as `vsprintf' does, which is rather preferable. */ wb_ensuremax(buf, add(buf->length, result < 0 ? mul(2, rest) : (size_t) result)); } if (result >= 0) buf->length += result; else buf->contents[buf->length] = L'\0'; assert(buf->contents[buf->length] == L'\0'); return result; } /* Appends the result of `swprintf' to the specified buffer. * `format' and the following arguments must not be part of `buf->contents'. * Returns the number of appended characters if successful. * On error, the buffer is not changed and -1 is returned. */ int wb_wprintf(xwcsbuf_T *restrict buf, const wchar_t *restrict format, ...) { va_list ap; int result; va_start(ap, format); result = wb_vwprintf(buf, format, ap); va_end(ap); return result; } /********** Multibyte-Wide Conversion Utilities **********/ /* Converts the specified wide string into a newly malloced multibyte string. * Only the first `n' characters of `s' is converted at most. * Returns NULL on error. * The resulting string starts and ends in the initial shift state.*/ char *malloc_wcsntombs(const wchar_t *s, size_t n) { xstrbuf_T buf; mbstate_t state; sb_init(&buf); memset(&state, 0, sizeof state); // initialize as the initial shift state if (sb_wcsncat(&buf, s, n, &state) == NULL) { return sb_tostr(&buf); } else { sb_destroy(&buf); return NULL; } } /* Converts the specified wide string into a newly malloced multibyte string. * Returns NULL on error. * The resulting string starts and ends in the initial shift state.*/ #if !HAVE_WCSNRTOMBS char *malloc_wcstombs(const wchar_t *s) { xstrbuf_T buf; mbstate_t state; sb_init(&buf); memset(&state, 0, sizeof state); // initialize as the initial shift state if (sb_wcscat(&buf, s, &state) == NULL) { return sb_tostr(&buf); } else { sb_destroy(&buf); return NULL; } } #endif /* Converts the specified multibyte string into a newly malloced wide string. * Returns NULL on error. */ wchar_t *malloc_mbstowcs(const char *s) { xwcsbuf_T buf; wb_init(&buf); if (wb_mbscat(&buf, s) == NULL) { return wb_towcs(&buf); } else { wb_destroy(&buf); return NULL; } } /********** Formatting Utilities **********/ /* Returns the result of `vsprintf' as a newly malloced string. * An error message is printed on error. The return value is non-NULL anyway. */ char *malloc_vprintf(const char *format, va_list ap) { xstrbuf_T buf; sb_init(&buf); if (sb_vprintf(&buf, format, ap) < 0) xerror(errno, Ngt("unexpected error")); return sb_tostr(&buf); } /* Returns the result of `sprintf' as a newly malloced string. * An error message is printed on error. The return value is non-NULL anyway. */ char *malloc_printf(const char *format, ...) { va_list ap; char *result; va_start(ap, format); result = malloc_vprintf(format, ap); va_end(ap); return result; } /* Returns the result of `vswprintf' as a newly malloced string. * An error message is printed on error. The return value is non-NULL anyway. */ wchar_t *malloc_vwprintf(const wchar_t *format, va_list ap) { xwcsbuf_T buf; wb_init(&buf); if (wb_vwprintf(&buf, format, ap) < 0) xerror(errno, Ngt("unexpected error")); return wb_towcs(&buf); } /* Returns the result of `swprintf' as a newly malloced string. * An error message is printed on error. The return value is non-NULL anyway. */ wchar_t *malloc_wprintf(const wchar_t *format, ...) { va_list ap; wchar_t *result; va_start(ap, format); result = malloc_vwprintf(format, ap); va_end(ap); return result; } /********** String Creating Utilities **********/ /* Joins the wide-character strings in the specified NULL-terminated array. The * array elements are considered pointers to wide strings. * `padding' is padded between each joined element. * Returns a newly malloced string. */ wchar_t *joinwcsarray(void *const *array, const wchar_t *padding) { size_t elemcount, ccount = 0; /* count the full length of the resulting string */ for (elemcount = 0; array[elemcount] != NULL; elemcount++) ccount = add(ccount, wcslen(array[elemcount])); if (elemcount > 0) ccount = add(ccount, mul(wcslen(padding), elemcount - 1)); /* do copying */ wchar_t *const result = xmalloce(ccount, 1, sizeof *result); wchar_t *s = result; for (size_t i = 0; i < elemcount; i++) { wchar_t *elem = array[i]; while (*elem != L'\0') *s++ = *elem++; if (i + 1 < elemcount) { const wchar_t *pad = padding; while (*pad != L'\0') *s++ = *pad++; } } *s = L'\0'; return result; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/strbuf.h000066400000000000000000000357141354143602500141070ustar00rootroot00000000000000/* Yash: yet another shell */ /* strbuf.h: modifiable string buffer */ /* (C) 2007-2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_STRBUF_H #define YASH_STRBUF_H #include #include #include #define Size_max ((size_t) -1) // = SIZE_MAX typedef struct xstrbuf_T { char *contents; size_t length, maxlength; } xstrbuf_T; typedef struct xwcsbuf_T { wchar_t *contents; size_t length, maxlength; } xwcsbuf_T; extern xstrbuf_T *sb_init(xstrbuf_T *buf) __attribute__((nonnull)); extern xstrbuf_T *sb_initwith(xstrbuf_T *restrict buf, char *restrict s) __attribute__((nonnull)); static inline void sb_destroy(xstrbuf_T *buf) __attribute__((nonnull)); static inline char *sb_tostr(xstrbuf_T *buf) __attribute__((nonnull)); extern xstrbuf_T *sb_setmax(xstrbuf_T *buf, size_t newmax) __attribute__((nonnull)); extern xstrbuf_T *sb_ensuremax(xstrbuf_T *buf, size_t max) __attribute__((nonnull)); static inline xstrbuf_T *sb_truncate(xstrbuf_T *buf, size_t newlength) __attribute__((nonnull)); static inline xstrbuf_T *sb_clear(xstrbuf_T *buf) __attribute__((nonnull)); extern xstrbuf_T *sb_replace_force( xstrbuf_T *restrict buf, size_t i, size_t bn, const char *restrict s, size_t sn) __attribute__((nonnull)); extern xstrbuf_T *sb_replace( xstrbuf_T *restrict buf, size_t i, size_t bn, const char *restrict s, size_t sn) __attribute__((nonnull)); static inline xstrbuf_T *sb_ninsert_force( xstrbuf_T *restrict buf, size_t i, const char *restrict s, size_t n) __attribute__((nonnull)); static inline xstrbuf_T *sb_ninsert( xstrbuf_T *restrict buf, size_t i, const char *restrict s, size_t n) __attribute__((nonnull)); static inline xstrbuf_T *sb_insert( xstrbuf_T *restrict buf, size_t i, const char *restrict s) __attribute__((nonnull)); static inline xstrbuf_T *sb_ncat_force( xstrbuf_T *restrict buf, const char *restrict s, size_t n) __attribute__((nonnull)); static inline xstrbuf_T *sb_ncat( xstrbuf_T *restrict buf, const char *restrict s, size_t n) __attribute__((nonnull)); static inline xstrbuf_T *sb_cat( xstrbuf_T *restrict buf, const char *restrict s) __attribute__((nonnull)); static inline xstrbuf_T *sb_catfree( xstrbuf_T *restrict buf, char *restrict s) __attribute__((nonnull)); static inline xstrbuf_T *sb_remove(xstrbuf_T *buf, size_t i, size_t n) __attribute__((nonnull)); extern xstrbuf_T *sb_ccat(xstrbuf_T *buf, char c) __attribute__((nonnull)); extern xstrbuf_T *sb_ccat_repeat(xstrbuf_T *buf, char c, size_t n) __attribute__((nonnull)); extern _Bool sb_wccat( xstrbuf_T *restrict buf, wchar_t c, mbstate_t *restrict ps) __attribute__((nonnull)); extern wchar_t *sb_wcsncat(xstrbuf_T *restrict buf, const wchar_t *restrict s, size_t n, mbstate_t *restrict ps) __attribute__((nonnull)); #if HAVE_WCSNRTOMBS static inline #else extern #endif wchar_t *sb_wcscat(xstrbuf_T *restrict buf, const wchar_t *restrict s, mbstate_t *restrict ps) __attribute__((nonnull)); extern int sb_vprintf( xstrbuf_T *restrict buf, const char *restrict format, va_list ap) __attribute__((nonnull(1,2),format(printf,2,0))); extern int sb_printf( xstrbuf_T *restrict buf, const char *restrict format, ...) __attribute__((nonnull(1,2),format(printf,2,3))); extern xwcsbuf_T *wb_init(xwcsbuf_T *buf) __attribute__((nonnull)); extern xwcsbuf_T *wb_initwith(xwcsbuf_T *restrict buf, wchar_t *restrict s) __attribute__((nonnull)); static inline void wb_destroy(xwcsbuf_T *buf) __attribute__((nonnull)); static inline wchar_t *wb_towcs(xwcsbuf_T *buf) __attribute__((nonnull)); extern xwcsbuf_T *wb_setmax(xwcsbuf_T *buf, size_t newmax) __attribute__((nonnull)); extern xwcsbuf_T *wb_ensuremax(xwcsbuf_T *buf, size_t max) __attribute__((nonnull)); static inline xwcsbuf_T *wb_truncate(xwcsbuf_T *buf, size_t newlength) __attribute__((nonnull)); static inline xwcsbuf_T *wb_clear(xwcsbuf_T *buf) __attribute__((nonnull)); extern xwcsbuf_T *wb_replace_force( xwcsbuf_T *restrict buf, size_t i, size_t bn, const wchar_t *restrict s, size_t sn) __attribute__((nonnull)); extern xwcsbuf_T *wb_replace( xwcsbuf_T *restrict buf, size_t i, size_t bn, const wchar_t *restrict s, size_t sn) __attribute__((nonnull)); static inline xwcsbuf_T *wb_ninsert_force( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s, size_t n) __attribute__((nonnull)); static inline xwcsbuf_T *wb_ninsert( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s, size_t n) __attribute__((nonnull)); static inline xwcsbuf_T *wb_insert( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s) __attribute__((nonnull)); static inline xwcsbuf_T *wb_ncat_force( xwcsbuf_T *restrict buf, const wchar_t *restrict s, size_t n) __attribute__((nonnull)); static inline xwcsbuf_T *wb_ncat( xwcsbuf_T *restrict buf, const wchar_t *restrict s, size_t n) __attribute__((nonnull)); static inline xwcsbuf_T *wb_cat( xwcsbuf_T *restrict buf, const wchar_t *restrict s) __attribute__((nonnull)); static inline xwcsbuf_T *wb_catfree( xwcsbuf_T *restrict buf, wchar_t *restrict s) __attribute__((nonnull)); static inline xwcsbuf_T *wb_remove(xwcsbuf_T *buf, size_t i, size_t n) __attribute__((nonnull)); extern xwcsbuf_T *wb_wccat(xwcsbuf_T *buf, wchar_t c) __attribute__((nonnull)); extern char *wb_mbscat(xwcsbuf_T *restrict buf, const char *restrict s) __attribute__((nonnull)); extern int wb_vwprintf( xwcsbuf_T *restrict buf, const wchar_t *restrict format, va_list ap) __attribute__((nonnull(1,2))); extern int wb_wprintf( xwcsbuf_T *restrict buf, const wchar_t *restrict format, ...) __attribute__((nonnull(1,2))); extern char *malloc_wcsntombs(const wchar_t *s, size_t n) __attribute__((nonnull,malloc,warn_unused_result)); #if HAVE_WCSNRTOMBS static inline #else extern #endif char *malloc_wcstombs(const wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); static inline char *realloc_wcstombs(wchar_t *s) __attribute__((nonnull,malloc,warn_unused_result)); extern wchar_t *malloc_mbstowcs(const char *s) __attribute__((nonnull,malloc,warn_unused_result)); static inline wchar_t *realloc_mbstowcs(char *s) __attribute__((nonnull,malloc,warn_unused_result)); extern char *malloc_vprintf(const char *format, va_list ap) __attribute__((nonnull(1),malloc,warn_unused_result,format(printf,1,0))); extern char *malloc_printf(const char *format, ...) __attribute__((nonnull(1),malloc,warn_unused_result,format(printf,1,2))); extern wchar_t *malloc_vwprintf(const wchar_t *format, va_list ap) __attribute__((nonnull(1),malloc,warn_unused_result)); extern wchar_t *malloc_wprintf(const wchar_t *format, ...) __attribute__((nonnull(1),malloc,warn_unused_result)); extern wchar_t *joinwcsarray(void *const *array, const wchar_t *padding) __attribute__((malloc,warn_unused_result,nonnull)); /* Frees the specified multibyte string buffer. The contents are lost. */ void sb_destroy(xstrbuf_T *buf) { free(buf->contents); } /* Frees the specified multibyte string buffer and returns the contents. * The caller must `free' the return value. */ char *sb_tostr(xstrbuf_T *buf) { return buf->contents; } /* Shrinks the length of the buffer to `newlength'. * `newlength' must not be larger than the current length. * Characters beyond the new length are lost. * `maxlength' of the buffer is not changed. */ xstrbuf_T *sb_truncate(xstrbuf_T *buf, size_t newlength) { #ifdef assert assert(newlength <= buf->length); #endif buf->contents[buf->length = newlength] = '\0'; return buf; } /* Clears the contents of the specified string buffer. * `maxlength' of the buffer is not changed. */ xstrbuf_T *sb_clear(xstrbuf_T *buf) { return sb_truncate(buf, 0); } /* Inserts the first `n' bytes of multibyte string `s' at offset `i' in buffer * `buf'. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_ninsert_force( xstrbuf_T *restrict buf, size_t i, const char *restrict s, size_t n) { return sb_replace_force(buf, i, 0, s, n); } /* Inserts the first `n' bytes of multibyte string `s' at offset `i' in buffer * `buf'. * If (strlen(s) <= n), the whole of `s' is inserted. * If (buf->length <= i), `s' is appended to the end of the buffer. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_ninsert( xstrbuf_T *restrict buf, size_t i, const char *restrict s, size_t n) { return sb_replace(buf, i, 0, s, n); } /* Inserts multibyte string `s' at offset `i' in buffer `buf'. * If (buf->length <= i), `s' is appended to the end of the buffer. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_insert(xstrbuf_T *restrict buf, size_t i, const char *restrict s) { return sb_replace(buf, i, 0, s, Size_max); } /* Appends the first `n' bytes of multibyte string `s' to buffer `buf'. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_ncat_force( xstrbuf_T *restrict buf, const char *restrict s, size_t n) { return sb_replace_force(buf, buf->length, 0, s, n); } /* Appends the first `n' bytes of multibyte string `s' to buffer `buf'. * If (strlen(s) <= n), the whole of `s' is appended. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_ncat(xstrbuf_T *restrict buf, const char *restrict s, size_t n) { return sb_replace(buf, Size_max, 0, s, n); } /* Appends multibyte string `s' to buffer `buf'. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_cat(xstrbuf_T *restrict buf, const char *restrict s) { return sb_replace(buf, Size_max, 0, s, Size_max); } /* Appends multibyte string `s' to buffer `buf' and free the string. * `s' must not be part of `buf->contents'. */ xstrbuf_T *sb_catfree(xstrbuf_T *restrict buf, char *restrict s) { sb_cat(buf, s); free(s); return buf; } /* Removes `n' bytes at offset `i' in buffer `buf'. * If (buf->length <= i), `buf' is unchanged. * If (buf->length <= i + n), `buf' is truncated to `i' bytes. */ xstrbuf_T *sb_remove(xstrbuf_T *buf, size_t i, size_t n) { return sb_replace(buf, i, n, "", 0); } #if HAVE_WCSNRTOMBS wchar_t *sb_wcscat(xstrbuf_T *restrict buf, const wchar_t *restrict s, mbstate_t *restrict ps) { return sb_wcsncat(buf, s, Size_max, ps); } #endif /* Frees the specified wide string buffer. The contents are lost. */ void wb_destroy(xwcsbuf_T *buf) { free(buf->contents); } /* Frees the specified wide string buffer and returns the contents. * The caller must `free' the return value. */ wchar_t *wb_towcs(xwcsbuf_T *buf) { return buf->contents; } /* Shrinks the length of the specified buffer to `newlength'. * `newlength' must not be larger than the current length. * Characters beyond the new length are lost. * `maxlength' of the buffer is not changed. */ xwcsbuf_T *wb_truncate(xwcsbuf_T *buf, size_t newlength) { #ifdef assert assert(newlength <= buf->length); #endif buf->contents[buf->length = newlength] = L'\0'; return buf; } /* Clears the contents of the specified string buffer. * `maxlength' of the buffer is not changed. */ xwcsbuf_T *wb_clear(xwcsbuf_T *buf) { return wb_truncate(buf, 0); } /* Inserts the first `n' characters of wide string `s' at offset `i' in buffer * `buf'. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_ninsert_force( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s, size_t n) { return wb_replace_force(buf, i, 0, s, n); } /* Inserts the first `n' characters of wide string `s` at offset `i' in buffer * `buf'. * If (wcslen(s) <= n), the whole of `s' is inserted. * If (buf->length <= i), `s' is appended to the end of the buffer. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_ninsert( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s, size_t n) { return wb_replace(buf, i, 0, s, n); } /* Inserts wide string `s' at offset `i' in buffer `buf'. * If (buf->length <= i), `s' is appended to the end of the buffer. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_insert( xwcsbuf_T *restrict buf, size_t i, const wchar_t *restrict s) { return wb_replace(buf, i, 0, s, Size_max); } /* Appends the first `n' characters of wide string `s' to buffer `buf'. * No boundary checks are done and null characters are not considered special. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_ncat_force( xwcsbuf_T *restrict buf, const wchar_t *restrict s, size_t n) { return wb_replace_force(buf, buf->length, 0, s, n); } /* Appends the first `n' characters of wide string `s' to buffer `buf'. * If (wcslen(s) <= n), the whole of `s' is appended. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_ncat(xwcsbuf_T *restrict buf, const wchar_t *restrict s, size_t n) { return wb_replace(buf, Size_max, 0, s, n); } /* Appends wide string `s' to buffer `buf'. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_cat(xwcsbuf_T *restrict buf, const wchar_t *restrict s) { return wb_replace(buf, Size_max, 0, s, Size_max); } /* Appends wide string `s' to buffer and frees the string. * `s' must not be part of `buf->contents'. */ xwcsbuf_T *wb_catfree(xwcsbuf_T *restrict buf, wchar_t *restrict s) { wb_cat(buf, s); free(s); return buf; } /* Removes `n' characters at offset `i' in buffer `buf'. * If (buf->length <= i), `buf' is unchanged. * If (buf->length <= i + n), `buf' is truncated to `i' characters. */ xwcsbuf_T *wb_remove(xwcsbuf_T *buf, size_t i, size_t n) { return wb_replace(buf, i, n, L"", 0); } #if HAVE_WCSNRTOMBS char *malloc_wcstombs(const wchar_t *s) { return malloc_wcsntombs(s, Size_max); } #endif /* Converts the specified wide string into a newly malloced multibyte string and * frees the original wide string. * Returns NULL on error. The wide string is freed anyway. * The resulting string starts and ends in the initial shift state.*/ char *realloc_wcstombs(wchar_t *s) { char *result = malloc_wcstombs(s); free(s); return result; } /* Converts the specified multibyte string into a newly malloced wide string and * frees the multibyte string. * Returns NULL on error. The multibyte string is freed anyway. */ wchar_t *realloc_mbstowcs(char *s) { wchar_t *result = malloc_mbstowcs(s); free(s); return result; } #undef Size_max #endif /* YASH_STRBUF_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/tests/000077500000000000000000000000001354143602500135615ustar00rootroot00000000000000yash-2.49/tests/Makefile.in000066400000000000000000000115761354143602500156400ustar00rootroot00000000000000# Makefile.in for test of yash # (C) 2007-2019 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . .POSIX: .SUFFIXES: .c .h .d .o .tst .trs @MAKE_SHELL@ topdir = .. subdir = tests CC = @CC@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ LDFLAGS = @LDFLAGS@ LDLIBS = @LDLIBS@ SOURCES = checkfg.c ptwrap.c resetsig.c POSIX_TEST_SOURCES = alias-p.tst andor-p.tst arith-p.tst async-p.tst bg-p.tst break-p.tst builtins-p.tst case-p.tst cd-p.tst cmdsub-p.tst command-p.tst comment-p.tst continue-p.tst dot-p.tst errexit-p.tst error-p.tst eval-p.tst exec-p.tst exit-p.tst export-p.tst fg-p.tst fnmatch-p.tst for-p.tst fsplit-p.tst function-p.tst getopts-p.tst grouping-p.tst if-p.tst input-p.tst job-p.tst kill1-p.tst kill2-p.tst kill3-p.tst kill4-p.tst lineno-p.tst nop-p.tst option-p.tst param-p.tst path-p.tst pipeline-p.tst ppid-p.tst quote-p.tst read-p.tst readonly-p.tst redir-p.tst return-p.tst set-p.tst shift-p.tst signal-p.tst simple-p.tst test-p.tst testtty-p.tst tilde-p.tst trap-p.tst umask-p.tst unset-p.tst until-p.tst wait-p.tst while-p.tst YASH_TEST_SOURCES = alias-y.tst andor-y.tst arith-y.tst array-y.tst async-y.tst bg-y.tst bindkey-y.tst brace-y.tst bracket-y.tst break-y.tst builtins-y.tst case-y.tst cd-y.tst cmdprint-y.tst cmdsub-y.tst command-y.tst complete-y.tst continue-y.tst dirstack-y.tst disown-y.tst dot-y.tst echo-y.tst errexit-y.tst error-y.tst errretur-y.tst eval-y.tst exec-y.tst exit-y.tst export-y.tst fc-y.tst fg-y.tst for-y.tst fsplit-y.tst function-y.tst getopts-y.tst grouping-y.tst hash-y.tst help-y.tst history-y.tst history1-y.tst history2-y.tst if-y.tst job-y.tst jobs-y.tst kill-y.tst lineno-y.tst local-y.tst option-y.tst param-y.tst pipeline-y.tst printf-y.tst prompt-y.tst pwd-y.tst quote-y.tst random-y.tst read-y.tst readonly-y.tst redir-y.tst return-y.tst set-y.tst settty-y.tst shift-y.tst signal1-y.tst signal2-y.tst signal3-y.tst signal4-y.tst signal5-y.tst signal6-y.tst signal7-y.tst signal8-y.tst signal9-y.tst simple-y.tst startup-y.tst suspend-y.tst test1-y.tst test2-y.tst tilde-y.tst times-y.tst trap-y.tst typeset-y.tst ulimit-y.tst umask-y.tst unset-y.tst until-y.tst wait-y.tst while-y.tst TEST_SOURCES = $(POSIX_TEST_SOURCES) $(YASH_TEST_SOURCES) TEST_RESULTS = $(TEST_SOURCES:.tst=.trs) RECHECK_LOGS = $(TEST_RESULTS) TARGET = @TARGET@ YASH = $(topdir)/$(TARGET) TESTERS = $(SOURCES:.c=) TESTEE = $(YASH) RUN_TEST = ./resetsig $(YASH) ./run-test.sh SUMMARY = summary.log BYPRODUCTS = $(SOURCES:.c=.o) $(TESTERS) $(TEST_RESULTS) $(SUMMARY) *.dSYM test: rm -rf $(RECHECK_LOGS) @$(MAKE) $(SUMMARY) @tail -n 6 $(SUMMARY) @tail -n 6 $(SUMMARY) | grep ^FAILED | \ (read _ fail_count && \ if [ "$$fail_count" -ne 0 ]; then \ echo See $(SUMMARY) for details; \ exit 1; \ fi) test-posix: @$(MAKE) TEST_SOURCES='$$(POSIX_TEST_SOURCES)' test test-yash: @$(MAKE) TEST_SOURCES='$$(YASH_TEST_SOURCES)' test test-valgrind: @$(MAKE) RUN_TEST='$(RUN_TEST) -v' test $(SUMMARY): $(TEST_RESULTS) $(SHELL) ./summarize.sh $(TEST_RESULTS) >| $@ $(TEST_RESULTS): $(TESTERS) $(YASH) .tst.trs: @set $(RUN_TEST) $(TESTEE) $<; \ if grep -q %REQUIRETTY% $<; then \ if ./ptwrap ./checkfg 2>/dev/null; then \ set ./ptwrap "$$@"; \ else \ set $(SHELL) ./enqueue.sh "$$@"; \ fi; \ fi; \ echo "$$*"; \ "$$@" tester: $(TESTERS) $(TESTERS): $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $@.c $(LDLIBS) $(YASH): @echo Make $(TARGET) in $(topdir) first >&2; false .c.o: @rm -f $@ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< DISTFILES = $(SOURCES) $(SOURCES:.c=.d) Makefile.in POSIX README enqueue.sh run-test.sh test-y.sh summarize.sh valgrind.supp distfiles: makedeps $(DISTFILES) copy-distfiles: distfiles mkdir -p $(topdir)/$(DISTTARGETDIR) cp $(DISTFILES) $(TEST_SOURCES) $(topdir)/$(DISTTARGETDIR) makedeps: _PHONY @(cd $(topdir) && $(MAKE) $(TARGET)) $(topdir)/$(TARGET) $(topdir)/makedeps.yash $(SOURCES) mostlyclean: rm -fr $(BYPRODUCTS) clean: mostlyclean distclean: clean rm -fr Makefile maintainer-clean: distclean rm -fr $(SOURCES:.c=.d) Makefile: Makefile.in $(topdir)/config.status @+(cd $(topdir) && $(MAKE) config.status) @(cd $(topdir) && $(SHELL) config.status $(subdir)/$@) .IGNORE: ptwrap .PHONY: test test-posix test-yash test-valgrind tester distfiles copy-distfiles makedeps mostlyclean clean distclean maintainer-clean _PHONY: @MAKE_INCLUDE@ checkfg.d @MAKE_INCLUDE@ ptwrap.d @MAKE_INCLUDE@ resetsig.d yash-2.49/tests/POSIX000066400000000000000000000062771354143602500144220ustar00rootroot00000000000000This document indicates which part of IEEE Std 1003.1, 2016 edition is covered by which test file. Volume 3: Shell & Utilities =========================== 1.1.2 Concepts Derived from the ISO C Standard Arithmetic Precision and Operations * arith-p.tst 2.2 Quoting * quote-p.tst * cmdsub-p.tst 2.3 Token Recognition * comment-p.tst * input-p.tst 2.3.1 Alias Substitution * alias-p.tst 2.4 Reserved Words * redir-y.tst 2.5.2 Special Parameters * param-p.tst 2.5.3 Shell Variables IFS * fsplit-p.tst LINENO * lineno-p.tst * lineno-y.tst PS1, PS2, PS4 * prompt-y.tst 2.6 Word Expansions Empty field removal * fsplit-p.tst 2.6.1 Tilde Expansion * tilde-p.tst 2.6.2 Parameter Expansion * param-p.tst 2.6.3 Command Substitution * cmdsub-p.tst 2.6.4 Arithmetic Expansion * arith-p.tst 2.6.5 Field Splitting * fsplit-p.tst 2.6.6 Pathname Expansion * path-p.tst 2.7 Redirection * redir-p.tst * redir-y.tst 2.8.1 Consequences of Shell Errors Shell language syntax error * error-p.tst Special built-in utility error * dot-p.tst * export-p.tst * readonly-p.tst * trap-p.tst * unset-p.tst Redirection error * error-p.tst * error-y.tst Variable assignment error * error-p.tst Expansion error * error-p.tst 2.8.2 Exit Status for Commands * error-p.tst 2.9.1 Simple Commands * builtins-p.tst * redir-p.tst * simple-p.tst Command Search and Execution * builtins-p.tst * builtins-y.tst * simple-p.tst 2.9.2 Pipelines * pipeline-p.tst * pipeline-y.tst 2.9.3 Lists * andor-p.tst * async-p.tst * job-p.tst 2.9.4 Compound Commands * case-p.tst * for-p.tst * grouping-p.tst * if-p.tst * until-p.tst * while-p.tst 2.9.5 Function Definition Command * function-p.tst 2.10.2 Shell Grammar Rules * alias-p.tst * case-p.tst * case-y.tst * redir-y.tst 2.11 Signals and Error Handling * async-p.tst * job-p.tst * signal-p.tst * wait-p.tst 2.12 Shell Execution Environment * trap-p.tst 2.13 Pattern Matching Notation 2.13.1 Patterns Matching a Single Character * fnmatch-p.tst 2.13.2 Patterns Matching Multiple Characters * fnmatch-p.tst 2.13.3 Patterns Used for Filename Expansion * path-p.tst 2.14 Special Built-In Utilities * builtins-p.tst break * break-p.tst * break-y.tst colon * nop-p.tst continue * continue-p.tst * continue-y.tst dot * dot-p.tst eval * eval-p.tst exit * exit-p.tst export * export-p.tst readonly * readonly-p.tst * readonly-y.tst return * return-p.tst set * errexit-p.tst * option-p.tst * set-p.tst * set-y.tst * settty-y.tst shift * shift-p.tst unset * unset-p.tst 4 Utilities alias * alias-p.tst bg * bg-p.tst cd * cd-p.tst command * command-p.tst false * nop-p.tst fg * fg-p.tst getopts * getopts-p.tst hash * hash-y.tst kill * kill1-p.tst * kill2-p.tst * kill3-p.tst * kill4-p.tst read * read-p.tst sh Stdin, Input files * input-p.tst Asynchronous events * signal-p.tst * signal1-y.tst * signal3-y.tst * signal5-y.tst * signal7-y.tst * signal9-y.tst test * test-p.tst * testtty-p.tst trap * trap-p.tst true * nop-p.tst umask * umask-p.tst unalias * alias-p.tst wait * wait-p.tst * wait-y.tst vim: set tw=78: yash-2.49/tests/README000066400000000000000000000035221354143602500144430ustar00rootroot00000000000000This directory includes automated test cases for yash. The test cases are grouped into POSIX tests and yash-specific tests, which are written in files named *-p.tst and *-y.tst, respectively. Every POSIX shell is supposed to pass the POSIX tests, so those test cases does not test any yash-specific behavior at all. To run the POSIX tests on a shell other than yash, run in this directory: $ make TESTEE= test-posix --------------------------------------------------------------------------- Some test cases are skipped by the test runner depending on the configuration of yash, user's privilege, etc. If the help built-in is disabled in the configuration, for example, tests for the help built-in are skipped. There is no configuration in which no tests are skipped; some tests require a root privilege while some require a non-root privilege. --------------------------------------------------------------------------- Test cases can be run in parallel if your make supports parallel build. Exceptionally, test cases that require a control terminal have to be run sequentially if a pseudo-terminal cannot be opened to obtain a control terminal that can be used for testing. In that case, you have to run the tests in the foreground process group so that they can make use of the current control terminal. Test case files containing such tests are marked with the %REQUIRETTY% keyword. --------------------------------------------------------------------------- To test yash with Valgrind, run "make test-valgrind" in this directory. Yash must have been built without enabling any of the following variables in config.h: * HAVE_PROC_SELF_EXE * HAVE_PROC_CURPROC_FILE * HAVE_PROC_OBJECT_AOUT Otherwise, some tests would fail after Valgrind is invoked as a shell where yash should be invoked. Some tests are skipped to avoid false failures. yash-2.49/tests/alias-p.tst000066400000000000000000000213161354143602500156460ustar00rootroot00000000000000# alias-p.tst: test of aliases for any POSIX-compliant shell posix="true" setup 'set -e' test_OE -e 0 'defining alias' alias a='echo ABC' __IN__ ( setup "alias a='echo ABC'" test_oE -e 0 'using alias' a a a __IN__ ABC ABC ABC __OUT__ test_OE -e 0 'redefining alias - exit status' alias a='echo BCD' __IN__ test_oE 'redefining alias - redefinition' alias a='echo BCD' a __IN__ BCD __OUT__ test_OE -e 0 'removing specific alias - exit status' alias true=false unalias true __IN__ test_OE -e 0 'removing specific alias - removal' alias true=false unalias true true __IN__ test_OE -e 0 'removing multiple aliases - exit status' alias true=a cat=b echo=c unalias true cat echo __IN__ test_oE -e 0 'removing multiple aliases - removal' alias true=a cat=b echo=c unalias true cat echo true echo ok | cat __IN__ ok __OUT__ test_OE -e 0 'removing all aliases - exit status' alias a=a b=b c=c unalias -a __IN__ test_OE 'removing all aliases - removal' alias a=a b=b c=c unalias -a alias __IN__ test_OE -e 0 'printing specific alias' alias a | grep -q '^a=' __IN__ test_oE -e 0 'reusing printed alias (simple)' save="$(alias a)" unalias a eval alias "$save" a __IN__ ABC __OUT__ test_oE -e 0 'reusing printed alias (complex quotation)' alias a='printf %s\\n \"['\\\'{'\\'}\\\'']\"' save="$(alias a)" unalias a eval alias "$save" a __IN__ "['{\}']" __OUT__ test_OE -e 0 'printing all aliases' alias b=b c=c e='echo OK' alias >save_alias_1 unalias -a IFS=' ' # trick to replace each newline in save_alias_1 with a space eval alias -- $(cat save_alias_1) alias >save_alias_2 diff save_alias_1 save_alias_2 __IN__ test_OE -e 0 'subshell inherits aliases' (alias a) | grep -q '^a=' __IN__ test_oE 'subshell cannot affect main shell' (alias a='echo BCD') a __IN__ ABC __OUT__ test_O -d -e n 'printing undefined alias is error' unalias -a alias a __IN__ test_O -d -e n 'removing undefined alias is error' alias true=false unalias true unalias true __IN__ test_oE 'using alias after assignment (simple)' alias s=sh a=A s -c 'echo $a' __IN__ A __OUT__ test_oE 'using alias after assignment (complex)' alias b=' b=B s '\''echo $a $b'\''; echo C' s=' sh -c ' a=A b __IN__ A B C __OUT__ test_OE 'using alias after redirection (simple)' alias e=echo >/dev/null e not_printed __IN__ test_OE 'using alias after redirection (complex)' alias e=' >&- c >/dev/null ' c=' echo ' /dev/null echo \> 3 < __OUT__ test_oE 'alias starting with blank' alias e=' echo' b=' { e B; }' e A b __IN__ A B __OUT__ test_oE 'alias ending with blank' alias c=cat e='echo ' e c c cat alias c='cat ' e c c cat alias echo='e x x ' x=. echo echo alias x='x . ' echo echo __IN__ cat c cat cat cat cat . x echo . x x . x . echo x . x . __OUT__ test_OE 'alias substitution can be part of an operator' alias lt='<' # The "lt" token followed by ">" becomes the "<>" redirection operator. lt>/dev/null >&0 echo not printed __IN__ test_oE 'aliases cannot substitute reserved words' alias if=: then=: else=: fi=: for=: in=: do=: done=: if true; then echo then; else echo else; fi for a in A; do echo $a; done __IN__ then A __OUT__ test_oE 'quoted aliases are not substituted' alias echo=: \echo backslash at head ech\o backslash at tail e'c'ho partial single-quotation 'echo' full single-quotation e"c"ho partial double-quotation "echo" full double-quotation __IN__ backslash at head backslash at tail partial single-quotation full single-quotation partial double-quotation full double-quotation __OUT__ test_oE 'line continuation in alias name' alias eeee=echo ee\ e\ e ok __IN__ ok __OUT__ test_oE 'line continuation between alias names (1)' alias echo='\ echo\ ' foo='\ bar\ ' bar=X echo \ foo __IN__ X __OUT__ test_oE 'line continuation between alias names (2)' alias eeee='echo\ ' eeee eeee __IN__ echo __OUT__ test_oE 'alias substitution to line continuation' alias e='echo ' bs='\' bsnl='\ ' e bs foo e bsnl bar __IN__ foo bar __OUT__ test_oE 'characters allowed in alias name' alias Aa0_!%,@=echo Aa0_!%,@ ok __IN__ ok __OUT__ test_oE 'recursive alias' alias echo='echo % ' e='echo echo' e ! # e ! # echo echo ! # echo % echo % ! __IN__ % echo % ! __OUT__ test_oE 'alias in command substitution' alias e=: func() { alias e=echo echo "$(e ok)" } func __IN__ ok __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/alias-y.tst000066400000000000000000000127521354143602500156630ustar00rootroot00000000000000# alias-y.tst: yash-specific test of aliases setup 'set -e' test_OE -e 0 'yash has no predefined aliases' alias __IN__ test_oE 'multi-line alias' alias e='echo 1 echo 2' e 3 __IN__ 1 2 3 __OUT__ test_oE 'alias in command substitution' alias e=: func() { alias e=echo echo "$(e not_printed)" } func __IN__ __OUT__ test_Oe -e 2 'semicolon after newline (for)' alias -g s=';' for i s do :; done __IN__ syntax error: `;' cannot appear on a new line __ERR__ #' #` ( posix="true" test_Oe -e 2 'reserved word esac as pattern (-o POSIX)' alias CASE='case esac in ( ' ESAC='Esac' Esac='esac' CASE ESAC ) echo not reached; esac __IN__ syntax error: an unquoted `esac' cannot be the first case pattern __ERR__ #' #` ) ( if ! testee -c 'command -v [[' >/dev/null; then skip="true" fi test_OE -e 0 'alias substitution after [[' # "!" is not keyword between "[[" and "]]" alias -g != [[ ! ! ! foo ]] && [[ ! foo = foo ]] && [[ ! -n foo ]] && [[ ! ( ! foo ! = ! foo ! ) ! && ! foo ! ]] && [[ ! '' ! || ! foo ! ]] __IN__ ) test_oE 'alias substitution after function keyword' alias fn='function ' f=g fn f { echo F; } g __IN__ F __OUT__ test_oE 'alias substitution after function name with grouping' alias fn='function f ' p='()' g='{ echo G; }' fn p { echo F; } f fn g f __IN__ F G __OUT__ test_oE 'alias substitution after function name with empty parentheses body' alias fn='function f ' p='() ' fn p p { echo this is not function body; } __IN__ this is not function body __OUT__ test_oE 'alias substitution after function open parenthesis' alias fn='function f (' p=')' fn p { echo F; } f __IN__ F __OUT__ test_oE 'alias substitution after function parentheses' alias fn='function f() ' g='{ echo G; }' fn g f __IN__ G __OUT__ test_oE -e 0 'global aliases' alias -g A=a B=b C=c -- ---=- echo C B A -A- -B- -C- \A "B" 'C' --- __IN__ c b a -A- -B- -C- A B C - __OUT__ test_oE -e 0 'global alias (substituting to line continuation)' alias --global eeee='echo\ ' eeee eeee __IN__ echo __OUT__ test_oE -e 0 'global alias (substituting to single-quotation)' alias --global sq="'" printf '[%s]\n' sq A ' sq;' __IN__ [ A ] [;] __OUT__ test_oE -e 0 'global alias (substituting to pipeline)' alias -g pipe_cat='| cat' echo A pipe_cat - __IN__ A __OUT__ test_oE -e 0 'global alias (substituting to semicolon)' alias -g semicolon=';' echo A semicolon echo A __IN__ A A __OUT__ test_oE -e 0 'global alias (substituting to parenthesis)' alias -g l='(' r=')' l echo A r __IN__ A __OUT__ test_OE -e 0 'global alias (substituting to redirection)' alias -g I='&- __IN__ test_O -d -e n 'alias built-in printing to closed stream with operands' alias \a e >&- __IN__ test_O -d -e n 'alias built-in printing to closed stream with -p' alias -p >&- __IN__ test_O -d -e n 'alias built-in printing to closed stream with -p & operands' alias -p \a e >&- __IN__ test_Oe -e n 'unalias built-in invalid option' unalias --no-such-option __IN__ unalias: `--no-such-option' is not a valid option __ERR__ #' #` test_Oe -e n 'unalias built-in invalid combination of -a and operands' unalias -a e __IN__ unalias: no operand is expected __ERR__ test_Oe -e n 'unalias built-in missing operand' unalias __IN__ unalias: this command requires an operand __ERR__ test_Oe -e n 'unalias built-in printing non-existing alias' unalias no_such_alias __IN__ unalias: no such alias `no_such_alias' __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/andor-p.tst000066400000000000000000000026441354143602500156630ustar00rootroot00000000000000# andor-p.tst: test of and-or lists for any POSIX-compliant shell posix="true" test_oE -e 0 '2-command list, success && success' echo 1 && echo 2 __IN__ 1 2 __OUT__ test_oE -e 0 '2-command list, success || success' echo 1 || echo 2 __IN__ 1 __OUT__ test_oE -e n '2-command list, failure && success' false && echo 2 __IN__ __OUT__ test_oE -e 0 '2-command list, failure || success' false || echo 2 __IN__ 2 __OUT__ test_oE -e n '2-command list, success && failure' echo 1 && false __IN__ 1 __OUT__ test_oE -e 0 '2-command list, success || failure' echo 1 || false __IN__ 1 __OUT__ test_oE -e n '2-command list, failure && failure' false && false __IN__ __OUT__ test_oE -e n '2-command list, failure || failure' false || false __IN__ __OUT__ test_oE '3-command list' false && echo foo || echo bar true || echo foo && echo bar __IN__ bar bar __OUT__ test_x -e 0 'exit status of list is from last-executed pipeline (success)' false && exit 1 || true || exit 1 || exit 2 __IN__ test_x -e 13 'exit status of list is from last-executed pipeline (failure)' true && (exit 1) || true || exit || exit && (exit 13) && exit 20 && exit 21 __IN__ test_o 'linebreak after &&' echo 1 && echo 2 && echo 3 __IN__ 1 2 3 __OUT__ test_o 'linebreak after ||' false || false || echo foo __IN__ foo __OUT__ test_o 'pipelines in list' ! false && ! true | false && echo foo | cat __IN__ foo __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/andor-y.tst000066400000000000000000000020361354143602500156670ustar00rootroot00000000000000# andor-y.tst: yash-specific test of and-or lists test_Oe -e 2 'no command before &&' && echo foo __IN__ syntax error: a command is missing before `&' __ERR__ #'` test_Oe -e 2 'no command before ||' || echo foo __IN__ syntax error: a command is missing before `|' __ERR__ #'` test_Oe -e 2 'no command after &&' echo foo && __IN__ syntax error: a command is missing at the end of input __ERR__ #'` test_Oe -e 2 'no command after ||' echo foo || __IN__ syntax error: a command is missing at the end of input __ERR__ #'` test_Oe -e 2 '&& followed by &&' echo foo && && echo bar __IN__ syntax error: a command is missing before `&' __ERR__ #'` test_Oe -e 2 '&& followed by ||' echo foo && || echo bar __IN__ syntax error: a command is missing before `|' __ERR__ #'` test_Oe -e 2 '|| followed by &&' echo foo || && echo bar __IN__ syntax error: a command is missing before `&' __ERR__ #'` test_Oe -e 2 '|| followed by ||' echo foo || || echo bar __IN__ syntax error: a command is missing before `|' __ERR__ #'` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/arith-p.tst000066400000000000000000000205071354143602500156650ustar00rootroot00000000000000# arith-p.tst: test of arithmetic expansion for any POSIX-compliant shell posix="true" setup -d # POSIX does not specify how the result of an arithmetic expansion should be # formatted. We assume the result is always formatted by 'printf "%ld"'. test_oE -e 0 'single constant' echoraw $((0)) $((1)) $((100)) $((020)) $((0x7F)) __IN__ 0 1 100 16 127 __OUT__ test_oE -e 0 'single variable' zero=0 one=1 hundred=100 plus_one=+1 minus_one=-1 echoraw $((zero)) $((one)) $((hundred)) $((plus_one)) $((minus_one)) __IN__ 0 1 100 1 -1 __OUT__ test_oE -e 0 'unset variable is considered 0 (direct)' unset x echoraw $((x)) __IN__ 0 __OUT__ test_oE -e 0 'unary sign operators' echoraw $((+1)) $((-1)) $((-+-2)) __IN__ 1 -1 2 __OUT__ test_oE -e 0 'unary negation operators' echoraw $((~0)) $((~1)) $((~2)) $((~-1)) $((~-2)) echoraw $((!0)) $((!1)) $((!2)) $((!-1)) $((!-2)) __IN__ -1 -2 -3 0 1 1 0 0 0 0 __OUT__ test_oE -e 0 'multiplicative operators' echoraw $((0 * 0)) $((1*0)) $((0*1)) $((1*1)) $((2*3)) $((-5*7)) echoraw $((0 / 1)) $((6/2)) $((-12/3)) $((35/-5)) $((-121/-11)) echoraw $((0 % 1)) $((1%1)) $((1%2)) $((47%7)) __IN__ 0 0 0 1 6 -35 0 3 -4 -7 11 0 0 1 5 __OUT__ test_oE -e 0 'additive operators' echoraw $((0 + 0)) $((0+1)) $((2+3)) $((5+-7)) $((-7+13)) $((-1+-2)) echoraw $((0 - 0)) $((0-1)) $((3-2)) $((5- -7)) $((-7-13)) $((-1- -2)) __IN__ 0 1 5 -2 6 -3 0 -1 1 12 -20 1 __OUT__ test_oE -e 0 'shift operators' echoraw $((0 << 0)) $((3<<2)) $((5<<3)) $((-2<<3)) echoraw $((0 >> 0)) $((15>>2)) $((43>>3)) $((-14>>3)) __IN__ 0 12 40 -16 0 3 5 -2 __OUT__ test_oE -e 0 'relational operators' echoraw $((0 < 0)) $((0 <= 0)) $((0 > 0)) $((0 >= 0)) echoraw $((-1< -1)) $((-1< 0)) $((-1< 1)) \ $(( 0< -1)) $(( 0< 0)) $(( 0< 1)) \ $(( 1< -1)) $(( 1< 0)) $(( 1< 1)) echoraw $((-1<=-1)) $((-1<=0)) $((-1<=1)) \ $(( 0<=-1)) $(( 0<=0)) $(( 0<=1)) \ $(( 1<=-1)) $(( 1<=0)) $(( 1<=1)) echoraw $((-1> -1)) $((-1> 0)) $((-1> 1)) \ $(( 0> -1)) $(( 0> 0)) $(( 0> 1)) \ $(( 1> -1)) $(( 1> 0)) $(( 1> 1)) echoraw $((-1>=-1)) $((-1>=0)) $((-1>=1)) \ $(( 0>=-1)) $(( 0>=0)) $(( 0>=1)) \ $(( 1>=-1)) $(( 1>=0)) $(( 1>=1)) __IN__ 0 1 0 1 0 1 1 0 0 1 0 0 0 1 1 1 0 1 1 0 0 1 0 0 0 1 0 0 1 1 0 1 0 0 1 1 0 1 1 1 __OUT__ test_oE -e 0 'equality operators' echoraw $((0 == 0)) $((1==0)) $((0==1)) $((1==1)) $((3==3)) $((2==3)) echoraw $((0 != 0)) $((1!=0)) $((0!=1)) $((1!=1)) $((3!=3)) $((2!=3)) __IN__ 1 0 0 1 1 0 0 1 1 0 0 1 __OUT__ test_oE -e 0 'bitwise operators' echoraw $((0 & 0)) $((3&5)) $((-13&5)) $((3&-11)) $((-13&-11)) echoraw $((0 ^ 0)) $((3^5)) $((-13^5)) $((3^-11)) $((-13^-11)) echoraw $((0 | 0)) $((3|5)) $((-13|5)) $((3|-11)) $((-13|-11)) __IN__ 0 1 1 1 -15 0 6 -10 -10 6 0 7 -9 -9 -9 __OUT__ test_oE -e 0 'logical operators' echoraw $((0 && 0)) $((3&&0)) $((0&&-5)) $((3&&-5)) echoraw $((0 || 0)) $((3||0)) $((0||-5)) $((3||-5)) echoraw $((0 ? 0 : 0)) $((0?1:2)) $((1?2:3)) $((-1?2:3)) __IN__ 0 0 0 1 0 1 1 1 0 2 2 2 __OUT__ test_oE -e 0 'conditional evaluation of && operator operand' a=0 echoraw $((1&&(a=5))) echoraw $((0&&(a=-5))) echoraw $a __IN__ 1 0 5 __OUT__ test_oE -e 0 'conditional evaluation of || operator operand' a=0 echoraw $((0||(a=5))) echoraw $((1||(a=-5))) echoraw $a __IN__ 1 1 5 __OUT__ test_oE -e 0 'conditional evaluation of ?: operator operand' a=0 b=0 echoraw $((1?(a=5):(b=-5))) echoraw $a $b a=0 b=0 echoraw $((0?(a=-5):(b=5))) echoraw $a $b __IN__ 5 5 0 5 0 5 __OUT__ test_oE -e 0 'assignment operators' a=0 b=2 c=15 d=46 e=3 f=3 g=7 h=30 i=3 j=3 k=3 echoraw $((a=5)) $((b*=3)) $((c/=3)) $((d%=7)) $((e+=5)) $((f-=5)) \ $((g<<=2)) $((h>>=2)) $((i&=5)) $((j^=5)) $((k|=5)) echoraw $a $b $c $d $e $f $g $h $i $j $k __IN__ 5 6 5 4 8 -2 28 7 1 6 7 5 6 5 4 8 -2 28 7 1 6 7 __OUT__ test_O -d -e n 'assigning to read-only variable' readonly a=3 echoraw $((a=5)) echoraw not reached __IN__ test_oE -e 0 'unset variable is considered 0 (assignment)' unset x echoraw $((a=x)) && echoraw $a __IN__ 0 0 __OUT__ test_oE 'operator precedence: unary and multiplicatives' echoraw $((!0*3)) $((~-1*3)) $((!1/2)) $((!1%1)) __IN__ 3 0 0 0 __OUT__ test_oE 'operator precedence: multiplicatives' echoraw - - $((2*1%2)) echoraw $((2/2*3)) $((12/6/2)) $((6/3%2)) echoraw $((7%4*2)) $((8%12/2)) $((7%2%3)) __IN__ - - 0 3 1 0 6 4 1 __OUT__ test_oE 'operator precedence: multiplicatives and additives' echoraw $((2*3+1)) $((2*3-1)) $((1/1+1)) $((5%1+1)) echoraw $((1+2*3)) $((9-2*3)) $((2+0/2)) $((1+5%1)) __IN__ 7 5 2 1 7 3 2 1 __OUT__ test_oE 'operator precedence: additives' echoraw $((2-1+3)) $((3-2-1)) __IN__ 4 0 __OUT__ test_oE 'operator precedence: additives and shifts' echoraw $((1+1<<2)) $((3-1<<2)) $((8+8>>2)) $((8-4>>2)) echoraw $((1<<1+1)) $((2<<1-1)) $((8>>1+1)) $((8>>1-1)) __IN__ 8 8 4 1 4 2 2 8 __OUT__ test_oE 'operator precedence: shifts' echoraw $((1<<2<<1)) $((1<<3>>1)) $((8>>2<<1)) $((8>>2>>1)) __IN__ 8 4 4 1 __OUT__ test_oE 'operator precedence: shifts and relationals' echoraw $((1<<1<0)) $((2>>1<=0)) $((1<<1>2)) $((2>>1>=2)) echoraw $((0<2>>1)) $((1<=1<<1)) $((1>1>>1)) $((1>=1<<1)) __IN__ 0 0 0 0 1 1 1 0 __OUT__ test_oE 'operator precedence: relationals' echoraw $((1< 2< 2)) $((0< 1<=2)) $((1< 2> 0)) $((1< 2>=0)) echoraw $((1<=2< 2)) $((0<=0<=0)) $((1<=2> 1)) $((1<=2>=2)) echoraw $((0> 0< 1)) $((0> 0<=1)) $((1> 2> 2)) $((1> 2>=0)) echoraw $((0>=0< 0)) $((0>=0<=1)) $((1>=2> 1)) $((1>=2>=2)) __IN__ 1 1 1 1 1 0 0 0 1 1 0 1 0 1 0 0 __OUT__ test_oE 'operator precedence: relationals and equalities' echoraw $((0<=0==0)) $((0<=0!=1)) $((0==0<=0)) $((0!=2<=1)) echoraw $((1< 0==0)) $((1< 0!=1)) $((0==0< 0)) $((1!=0< 0)) echoraw $((0>=0==0)) $((1>=2!=0)) $((0==0>=0)) $((1!=0>=0)) echoraw $((0> 0==0)) $((0> 0!=1)) $((0==0> 1)) $((1!=0> 1)) __IN__ 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 __OUT__ test_oE 'operator precedence: equalities' echoraw $((0==0==2)) $((0==0!=2)) $((2!=0==0)) $((0!=2!=2)) __IN__ 0 1 0 1 __OUT__ test_oE 'operator precedence: equalities and bitwise and' echoraw $((0==0&0)) $((0&0==0)) $((1!=0&0)) $((0&0!=1)) __IN__ 0 0 0 0 __OUT__ test_oE 'operator precedence: bitwise and and xor' echoraw $((0&0^1)) $((1^0&0)) __IN__ 1 1 __OUT__ test_oE 'operator precedence: bitwise xor and or' echoraw $((1^1|1)) $((1|1^1)) __IN__ 1 1 __OUT__ test_oE 'operator precedence: bitwise or and logical and' echoraw $((1|1&&0)) $((0&&1|1)) __IN__ 0 0 __OUT__ test_oE 'operator precedence: logical and and or' echoraw $((0&&0||1)) $((1||0&&0)) __IN__ 1 1 __OUT__ test_oE 'operator precedence: logical or and conditional' echoraw $((2||0?0:1)) $((1?0:1||1)) __IN__ 0 0 __OUT__ test_oE 'operator precedence: conditionals' echoraw $((4?1:0?2:3)) __IN__ 1 __OUT__ test_oE 'operator precedence: assignments in conditionals' b=5 c=6 d=5 e=0 f=0 g=1 h=8 i=7 j=0 k=0 echoraw $((1?a=2:3)) $((1?b*=2:3)) $((1?c/=2:3)) $((1?d%=2:3)) \ $((1?e+=2:3)) $((1?f-=2:3)) $((1?g<<=2:3)) $((1?h>>=2:3)) \ $((1?i&=2:4)) $((1?j^=2:4)) $((1?k|=2:4)) echoraw $a $b $c $d $e $f $g $h $i $j $k __IN__ 2 10 3 1 2 -2 4 2 2 2 2 2 10 3 1 2 -2 4 2 2 2 2 __OUT__ test_oE 'operator precedence: conditionals and assignments' b=5 c=6 d=5 e=0 f=0 g=1 h=8 i=7 j=0 k=0 echoraw $((a=1?2:3)) $((b*=1?2:3)) $((c/=1?2:3)) $((d%=1?2:3)) \ $((e+=1?2:3)) $((f-=1?2:3)) $((g<<=1?2:3)) $((h>>=1?2:3)) \ $((i&=1?2:4)) $((j^=1?2:4)) $((k|=1?2:4)) echoraw $a $b $c $d $e $f $g $h $i $j $k __IN__ 2 10 3 1 2 -2 4 2 2 2 2 2 10 3 1 2 -2 4 2 2 2 2 __OUT__ test_oE 'operator precedence: assignments' b=5 c=10 d=5 e=10 f=10 g=16 h=2 i=3 j=1 k=2 echoraw $((a=b*=2)) $((c/=d%=3)) $((e+=f-=1)) $((g<<=h>>=1)) $((i&=j^=k|=1)) echoraw $a $b $c $d $e $f $g $h $i $j $k __IN__ 10 5 19 32 2 10 10 5 2 19 9 32 1 2 2 3 __OUT__ test_oE 'parentheses' echoraw $(((7))) $((-(-3))) $(((1+2)*5)) $((15/(7%4))) $(((0?1:0)?1:(a=2))) __IN__ 7 3 15 5 2 __OUT__ test_oE 'parameter expansion in arithmetic expansion' a=+123 echoraw $(($a)) $((${a%3})) $(($a-23)) __IN__ 123 12 100 __OUT__ test_oE 'command substitution in arithmetic expansion' echoraw $(($(echo 123))) $((1+$(echo 10)+`echo 100`+1000)) __IN__ 123 1111 __OUT__ # No shell except yash seems to support quote removal in arithmetic expansion. #test_oE 'quote removal' #echoraw $((1"0"0 + '2'0 + \3)) #__IN__ #123 #__OUT__ test_oE 'assignment in parameter expansion in arithmetic expansion' unset a echoraw $((${a=1})) echoraw $a __IN__ 1 1 __OUT__ test_O -d -e n 'malformed arithmetic expansion' echoraw $((--)) __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/arith-y.tst000066400000000000000000000157641354143602500157070ustar00rootroot00000000000000# arith-y.tst: yash-specific test of arithmetic expansion setup -d test_oE -e 0 'single variable' unset unset empty= zero=0.0 one=1.0 nonnumeric='hello world' echoraw "[$((empty))]" $((zero)) $((one)) "$((nonnumeric))" $((unset)) __IN__ [] 0.0 1.0 hello world 0 __OUT__ test_oE 'integer: prefix ++' x=1 echo $((++x)) echo $((++x)) echo $((++x)) echo $x __IN__ 2 3 4 4 __OUT__ test_oE 'integer: prefix --' x=1 echo $((--x)) echo $((--x)) echo $((--x)) echo $x __IN__ 0 -1 -2 -2 __OUT__ test_oE 'integer: postfix ++' x=1 echo $((x++)) echo $((x++)) echo $((x++)) echo $x __IN__ 1 2 3 4 __OUT__ test_oE 'integer: postfix --' x=1 echo $((x--)) echo $((x--)) echo $((x--)) echo $x __IN__ 1 0 -1 -2 __OUT__ # $1 = line no. # $2 = arithmetic expression that causes division by zero test_division_by_zero() { testcase "$1" -e 2 "division by zero ($2)" 3<<__IN__ 4>1.0)) $((3.0>>1)) $((3>>1.0)) __IN__ 10 10 10 1 1 1 __OUT__ test_oE -e 0 'float: relational operators: 1' echoraw $((0.0 < 0.0)) $((0.0 <= 0.0)) $((0.0 > 0.0)) $((0.0 >= 0.0)) echoraw $((0 < 0.0)) $((0 <= 0.0)) $((0 > 0.0)) $((0 >= 0.0)) echoraw $((0.0 < 0 )) $((0.0 <= 0 )) $((0.0 > 0 )) $((0.0 >= 0 )) __IN__ 0 1 0 1 0 1 0 1 0 1 0 1 __OUT__ test_oE -e 0 'float: relational operators: 2' echoraw $((.25 < .75)) $((.25 <= .75)) $((.25 > .75)) $((.25 >= .75)) echoraw $((0 < .75)) $((0 <= .75)) $((0 > .75)) $((0 >= .75)) echoraw $((.25 < 1 )) $((.25 <= 1 )) $((.25 > 1 )) $((.25 >= 1 )) __IN__ 1 1 0 0 1 1 0 0 1 1 0 0 __OUT__ test_oE -e 0 'float: relational operators: 3' echoraw $((.75 < .25)) $((.75 <= .25)) $((.75 > .25)) $((.75 >= .25)) echoraw $((1 < .25)) $((1 <= .25)) $((1 > .25)) $((1 >= .25)) echoraw $((.75 < 0 )) $((.75 <= 0 )) $((.75 > 0 )) $((.75 >= 0 )) __IN__ 0 0 1 1 0 0 1 1 0 0 1 1 __OUT__ test_oE -e 0 'float: equality operators' echoraw $((0.0==0.0)) $((0.0==0.1)) $((0.1==0.0)) $((0.1==0.1)) echoraw $((0 ==0.0)) $((0 ==0.1)) $((1 ==0.0)) $((1 ==0.1)) echoraw $((0.0==0 )) $((0.0==1 )) $((0.1==0 )) $((0.1==1 )) echoraw $((0.0!=0.0)) $((0.0!=0.1)) $((0.1!=0.0)) $((0.1!=0.1)) echoraw $((0 !=0.0)) $((0 !=0.1)) $((1 !=0.0)) $((1 !=0.1)) echoraw $((0.0!=0 )) $((0.0!=1 )) $((0.1!=0 )) $((0.1!=1 )) __IN__ 1 0 0 1 1 0 0 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 1 __OUT__ test_oE -e 0 'float: bitwise operators' echoraw $((10.75 & 12.25)) $((10.75 & 12)) $((10 & 12.25)) echoraw $((10.75 ^ 12.25)) $((10.75 ^ 12)) $((10 ^ 12.25)) echoraw $((10.75 | 12.25)) $((10.75 | 12)) $((10 | 12.25)) __IN__ 8 8 8 6 6 6 14 14 14 __OUT__ test_oE -e 0 'float: logical operators' echoraw $((0.1 && 0.5)) $((0.0 && 0.5)) $((0.1 && 0.0)) $((0.0 && 0.0)) echoraw $((0.1 || 0.5)) $((0.0 || 0.5)) $((0.1 || 0.0)) $((0.0 || 0.0)) a=A b=B echoraw $((0.1 ? a : b)) $((0.0 ? a : b)) __IN__ 1 0 0 0 1 1 1 0 A B __OUT__ test_oE -e 0 'float: assignment operator =' a=1 b=1.2 c=1.2 d=foo echoraw $((a=2.5)) $((b=3.75)) $((c=9)) $((d=1.25)) echoraw $a $b $c $d __IN__ 2.5 3.75 9 1.25 2.5 3.75 9 1.25 __OUT__ test_oE -e 0 'float: assignment operator *=' a=1 b=1.5 c=1.5 echoraw $((a*=2.5)) $((b*=3.75)) $((c*=9)) echoraw $a $b $c __IN__ 2.5 5.625 13.5 2.5 5.625 13.5 __OUT__ test_oE -e 0 'float: assignment operator /=' a=50 b=3.75 c=2.5 echoraw $((a/=1.25)) $((b/=1.5)) $((c/=2)) echoraw $a $b $c __IN__ 40 2.5 1.25 40 2.5 1.25 __OUT__ test_oE -e 0 'float: assignment operator %=' a=7 b=7.25 c=5.5 echoraw $((a%=1.25)) $((b%=1.25)) $((c%=2)) echoraw $a $b $c __IN__ 0.75 1 1.5 0.75 1 1.5 __OUT__ test_oE -e 0 'float: assignment operator +=' a=7 b=7.25 c=5.5 echoraw $((a+=1.25)) $((b+=1.25)) $((c+=2)) echoraw $a $b $c __IN__ 8.25 8.5 7.5 8.25 8.5 7.5 __OUT__ test_oE -e 0 'float: assignment operator -=' a=7 b=7.25 c=5.5 echoraw $((a-=1.25)) $((b-=1.5)) $((c-=2)) echoraw $a $b $c __IN__ 5.75 5.75 3.5 5.75 5.75 3.5 __OUT__ test_oE -e 0 'float: assignment operator <<=' a=10 b=10.9 c=10.9 echoraw $((a<<=2.9)) $((b<<=2.9)) $((c<<=2)) echoraw $a $b $c __IN__ 40 40 40 40 40 40 __OUT__ test_oE -e 0 'float: assignment operator >>=' a=42 b=42.9 c=42.9 echoraw $((a>>=2.9)) $((b>>=2.9)) $((c>>=2)) echoraw $a $b $c __IN__ 10 10 10 10 10 10 __OUT__ test_oE -e 0 'float: assignment operator &=' a=10 b=10.75 c=10.75 echoraw $((a&=12.25)) $((b&=12.25)) $((c&=12)) echoraw $a $b $c __IN__ 8 8 8 8 8 8 __OUT__ test_oE -e 0 'float: assignment operator ^=' a=10 b=10.75 c=10.75 echoraw $((a^=12.25)) $((b^=12.25)) $((c^=12)) echoraw $a $b $c __IN__ 6 6 6 6 6 6 __OUT__ test_oE -e 0 'float: assignment operator |=' a=10 b=10.75 c=10.75 echoraw $((a|=12.25)) $((b|=12.25)) $((c|=12)) echoraw $a $b $c __IN__ 14 14 14 14 14 14 __OUT__ test_Oe -e 2 'empty arithmetic expansion' eval '$(())' __IN__ eval: arithmetic: a value is missing __ERR__ ( posix=true test_Oe -e 2 'float literal in POSIXly-correct mode' eval 'echoraw $((1.5))' __IN__ eval: arithmetic: `1.5' is not a valid number __ERR__ #' #` test_Oe -e 2 'float variable in POSIXly-correct mode' a=1.5 eval 'echoraw $((-a))' __IN__ eval: arithmetic: `1.5' is not a valid number __ERR__ #' #` test_Oe -e 2 'prefix ++ in POSIXly-correct mode' eval 'echoraw $((++a))' __IN__ eval: arithmetic: operator `++' is not supported __ERR__ #' #` test_Oe -e 2 'prefix -- in POSIXly-correct mode' eval 'echoraw $((--a))' __IN__ eval: arithmetic: operator `--' is not supported __ERR__ #' #` test_Oe -e 2 'postfix ++ in POSIXly-correct mode' eval 'echoraw $((a++))' __IN__ eval: arithmetic: operator `++' is not supported __ERR__ #' #` test_Oe -e 2 'postfix -- in POSIXly-correct mode' eval 'echoraw $((a--))' __IN__ eval: arithmetic: operator `--' is not supported __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/array-y.tst000066400000000000000000000140651354143602500157070ustar00rootroot00000000000000# array-y.tst: yash-specific test of arrays setup -d setup - <<\__END__ set -e __END__ test_oE -e 0 'assigning empty array' e=() bracket "$e" __IN__ __OUT__ test_oE -e 0 'assigning array with empty expansion' e=($_empty) bracket "$e" __IN__ __OUT__ test_oE -e 0 'assigning non-empty array' set 1 '2 2' 3 a='4 5' a=("$@" $a) bracket "$a" __IN__ [1][2 2][3][4][5] __OUT__ test_oE -e 0 'multi-line array assignment' set 1 '2 2' 3 a='4 5' a=( "$@" $a ) b=b c=(c) bracket "$a" "$b" "$c" __IN__ [1][2 2][3][4][5][b][c] __OUT__ test_oE -e 0 'array value containing parentheses' a=(\)\() bracket "$a" __IN__ [)(] __OUT__ test_oE -e 0 'comment in array value' a=A b=( $a # 1 # 2 B ) bracket "$b" __IN__ [A][B] __OUT__ test_Oe -e 2 'unclosed array assignment' a=( __IN__ syntax error: `)' is missing __ERR__ #' #` test_Oe -e 2 'unquoted symbol in array assignment' a=(1;) __IN__ syntax error: `)' is missing __ERR__ #' #` test_oE -e 0 'reassigning array' a=(a) a=(b c) bracket "$a" __IN__ [b][c] __OUT__ # Below are tests of the array built-in. if ! testee --version --verbose | grep -Fqx ' * array'; then skip="true" fi test_OE -e 0 'printing all arrays (none defined)' array __IN__ ( setup - <<\__END__ a=(1 '2 2' 3) e=() b=(ABC) __END__ test_oE -e 0 'printing all arrays (some defined)' array __IN__ a=(1 '2 2' 3) b=(ABC) e=() __OUT__ test_O -d -e n 'printing all arrays to closed stream' array >&- __IN__ test_oE -e 0 'defining array' array b array c x 'y y' z bracket "$b" bracket "$c" __IN__ [x][y y][z] __OUT__ test_Oe -e n 'defining array (overwriting read-only array)' readonly a array a A B C __IN__ array: $a is read-only __ERR__ test_Oe -e n 'defining array (invalid name)' array a= A B C __IN__ array: `a=' is not a valid array name __ERR__ #' #` ( setup 'c=(1 2 3 4 5 6 7 8 9 10)' test_oE -e 0 'deleting array elements' array -d a array -d b 1 array --delete -- c 2 5 -2 11 -100 array --delete -- e -1 0 1 array __IN__ a=(1 '2 2' 3) b=() c=(1 3 4 6 7 8 10) e=() __OUT__ test_oE -e 0 'deleting array elements (index ordering)' array -d -- c -2 11 2 -100 5 11 bracket "$c" __IN__ [1][3][4][6][7][8][10] __OUT__ ) test_Oe -e n 'deleting array elements (nonexistent array)' array -d x __IN__ array: no such array $x __ERR__ test_Oe -e n 'deleting array elements (read-only array)' readonly a array -d a 1 __IN__ array: $a is read-only __ERR__ test_Oe -e n 'deleting array elements (invalid name)' array -d = 1 __IN__ array: `=' is not a valid array name __ERR__ #' #` test_Oe -e n 'deleting array elements (missing operand)' array -d __IN__ array: this command requires an operand __ERR__ test_oE -e 0 'inserting array elements (middle, none)' array -i a 2 bracket "$a" __IN__ [1][2 2][3] __OUT__ test_oE -e 0 'inserting array elements (middle, one)' array --insert a 2 I bracket "$a" __IN__ [1][2 2][I][3] __OUT__ test_oE -e 0 'inserting array elements (middle, some)' array -i a 2 I J K bracket "$a" __IN__ [1][2 2][I][J][K][3] __OUT__ test_oE -e 0 'inserting array elements (head, positive)' array -i a 0 I J bracket "$a" __IN__ [I][J][1][2 2][3] __OUT__ test_oE -e 0 'inserting array elements (tail, positive)' array -i a 3 I J bracket "$a" __IN__ [1][2 2][3][I][J] __OUT__ test_oE -e 0 'inserting array elements (over-tail, positive)' array -i a 4 I J bracket "$a" __IN__ [1][2 2][3][I][J] __OUT__ test_oE -e 0 'inserting array elements (tail, negative)' array -i -- a -1 I J bracket "$a" __IN__ [1][2 2][3][I][J] __OUT__ test_oE -e 0 'inserting array elements (head, negative)' array -i -- a -4 I J bracket "$a" __IN__ [I][J][1][2 2][3] __OUT__ test_oE -e 0 'inserting array elements (over-head, negative)' array -i -- a -5 I J bracket "$a" __IN__ [I][J][1][2 2][3] __OUT__ test_Oe -e n 'inserting array elements (nonexistent array)' array -i x 1 '' __IN__ array: no such array $x __ERR__ test_Oe -e n 'inserting array elements (read-only array)' readonly a array -i a 1 A __IN__ array: $a is read-only __ERR__ test_Oe -e n 'inserting array elements (invalid name)' array -i = 1 A __IN__ array: `=' is not a valid array name __ERR__ #' #` test_Oe -e n 'inserting array elements (missing operand)' array -i a __IN__ array: this command requires 2 operands __ERR__ test_oE -e 0 'setting array element (head, positive)' array -s a 1 A bracket "$a" __IN__ [A][2 2][3] __OUT__ test_oE -e 0 'setting array element (tail, positive)' array --set a 3 C bracket "$a" __IN__ [1][2 2][C] __OUT__ test_Oe -e n 'setting array element (over-tail, positive)' array -s a 4 C __IN__ array: index 4 is out of range (the actual size of array $a is 3) __ERR__ test_Oe -e n 'setting array element (index zero)' array -s a 0 C __IN__ array: index 0 is out of range (the actual size of array $a is 3) __ERR__ test_oE -e 0 'setting array element (tail, negative)' array -s a -1 C bracket "$a" __IN__ [1][2 2][C] __OUT__ test_oE -e 0 'setting array element (head, negative)' array -s a -3 A bracket "$a" __IN__ [A][2 2][3] __OUT__ test_Oe -e n 'setting array element (over-head, negative)' array -s a -4 C __IN__ array: index -4 is out of range (the actual size of array $a is 3) __ERR__ test_Oe -e n 'setting array element (empty array)' array -s e 1 '' __IN__ array: index 1 is out of range (the actual size of array $e is 0) __ERR__ test_Oe -e n 'setting array element (nonexistent array)' array -s x 1 '' __IN__ array: no such array $x __ERR__ test_Oe -e n 'setting array element (read-only array)' readonly a array -s a 1 A __IN__ array: $a is read-only __ERR__ test_Oe -e n 'setting array element (invalid name)' array -s = 1 A __IN__ array: `=' is not a valid array name __ERR__ #' #` test_Oe -e n 'setting array element (too many operands)' array -s a 1 A B __IN__ array: too many operands are specified __ERR__ test_Oe -e n 'setting array element (too few operands)' array -s a 1 __IN__ array: this command requires 3 operands __ERR__ ) test_Oe -e n 'invalid option' array --no-such-option __IN__ array: `--no-such-option' is not a valid option __ERR__ #' #` test_Oe -e 2 'no array assignment in POSIX mode' --posix foo=() __IN__ syntax error: invalid use of `(' __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/async-p.tst000066400000000000000000000027341354143602500156750ustar00rootroot00000000000000# async-p.tst: test of asynchronous lists for any POSIX-compliant shell posix="true" test_o 'synchronous lists separated by semicolons' echo 1; echo 2;echo 3; echo 4 echo 5 __IN__ 1 2 3 4 5 __OUT__ test_o 'asynchronous lists separated by ampersands' echo& echo &echo& wait __IN__ __OUT__ test_o 'asynchronous commands run asynchronously' # Note the blocking nature of opening a FIFO mkfifo fifo1 fifo2 echo foo >fifo1 & cat fifo1 >fifo2 & cat fifo2 & wait $! __IN__ foo __OUT__ test_o 'asynchronous command runs in subshell' a=1 { a=2; echo $a; }& wait $! echo $a __IN__ 2 1 __OUT__ test_oE 'stdin of asynchronous list is null without job control' +m cat& wait echo this line should not be consumed by cat __IN__ this line should not be consumed by cat __OUT__ # This test is in job-p.tst. #test_oE 'stdin of asynchronous list is not modified with job control' -m test_oE -e 0 'asynchronous list ignores SIGINT' "$TESTEE" -c 'kill -s INT $$; echo ok' & wait $! __IN__ ok __OUT__ test_oE -e 0 'asynchronous list ignores SIGQUIT' "$TESTEE" -c 'kill -s QUIT $$; echo ok' & wait $! __IN__ ok __OUT__ # These tests are in job-p.tst. #test_oE 'asynchronous list retains SIGINT trap with job control' -m #test_oE 'asynchronous list retains SIGQUIT trap with job control' -m test_o 'exit status of asynchronous list' true& echo $? false& echo $? __IN__ 0 0 __OUT__ test_o 'asynchronous and-or lists' a=1 a=2 && echo $a& wait echo $a __IN__ 2 1 __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/async-y.tst000066400000000000000000000007071354143602500157040ustar00rootroot00000000000000# async-y.tst: yash-specific test of asynchronous lists test_Oe -e 2 'no command before ;' ; __IN__ syntax error: a command is missing before `;' __ERR__ #'` test_Oe -e 2 'no command before &' & __IN__ syntax error: a command is missing before `&' __ERR__ #'` test_Oe -e 2 'no separator between commands' if true; then echo 1; fi if true; then echo 2; fi __IN__ syntax error: `;' or `&' is missing __ERR__ #'`#` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/bg-p.tst000066400000000000000000000026701354143602500151470ustar00rootroot00000000000000# bg-p.tst: test of the bg built-in for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% posix="true" cat >job1 <<\__END__ exec sh -c 'kill -s STOP $$; echo' __END__ chmod a+x job1 ln job1 job2 test_O -d -e n 'bg cannot be used when job control is disabled' +m :& bg __IN__ test_oE 'default operand chooses most recently suspended job' -m :& sh -c 'kill -s STOP $$; echo 1' bg >/dev/null wait __IN__ 1 __OUT__ test_OE 'already running job is ignored' -m while kill -s CONT $$; do sleep 1; done & bg >/dev/null kill % __IN__ test_OE -e 17 'resumed job is awaitable' -m sh -c 'kill -s STOP $$; exit 17' bg >/dev/null wait % __IN__ test_oE 'resumed job is in background' -m sh -c 'kill -s STOP $$; ../checkfg || echo bg' bg >/dev/null wait % __IN__ bg __OUT__ test_oE 'specifying job ID' -m ./job1 ./job2 echo - bg %./job1 >/dev/null bg %./job2 >/dev/null wait __IN__ - __OUT__ test_oE 'specifying more than one job ID' -m ./job1 ./job2 echo - bg %./job1 %./job2 >/dev/null wait __IN__ - __OUT__ test_OE -e 0 'bg prints resumed job' -m sleep 1& bg >bg.out grep -qx '\[[[:digit:]][[:digit:]]*][[:blank:]]*sleep 1' bg.out __IN__ test_OE -e 0 'exit status of bg' -m sh -c 'kill -s STOP $$; exit 17' bg >/dev/null __IN__ test_O -d -e n 'no existing job' -m bg __IN__ test_O -d -e n 'no such job' -m sh -c 'kill -s STOP $$' bg %_no_such_job_ exit_status=$? fg >/dev/null exit $exit_status __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/bg-y.tst000066400000000000000000000014151354143602500151540ustar00rootroot00000000000000# bg-y.tst: yash-specific test of the bg built-in ../checkfg || skip="true" # %REQUIRETTY% test_Oe -e 1 'non-job-controlled job (default operand)' :& set -m bg __IN__ bg: the current job is not a job-controlled job __ERR__ test_Oe -e 1 'non-job-controlled job (job ID operand)' :& set -m bg %: __IN__ bg: `%:' is not a job-controlled job __ERR__ #` test_Oe -e 1 'no such job (name)' -m : _no_such_job_& bg %_no_such_job_ __IN__ bg: no such job `%_no_such_job_' __ERR__ #` test_Oe -e 1 'no such job (number)' -m bg %2 __IN__ bg: no such job `%2' __ERR__ #` test_O -d -e 1 'printing to closed stream' -m :& bg >&- __IN__ test_Oe -e 2 'invalid option' -m bg --no-such-option __IN__ bg: `--no-such-option' is not a valid option __ERR__ #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/bindkey-y.tst000066400000000000000000000313241354143602500162130ustar00rootroot00000000000000# bindkey-y.tst: yash-specific test of the bindkey built-in if ! testee -c 'command -bv bindkey' >/dev/null; then skip="true" fi test_oE -e 0 'bindkey is a semi-special built-in' command -V bindkey __IN__ bindkey: a semi-special built-in __OUT__ sort -k 1 >commands <<\__END__ noop alert self-insert insert-tab expect-verbatim digit-argument bol-or-digit accept-line abort-line eof eof-if-empty eof-or-delete accept-with-hash accept-prediction setmode-viinsert setmode-vicommand setmode-emacs expect-char abort-expect-char redraw-all clear-and-redraw-all forward-char backward-char forward-bigword end-of-bigword backward-bigword forward-semiword end-of-semiword backward-semiword forward-viword end-of-viword backward-viword forward-emacsword backward-emacsword beginning-of-line end-of-line go-to-column first-nonblank find-char find-char-rev till-char till-char-rev refind-char refind-char-rev delete-char delete-bigword delete-semiword delete-viword delete-emacsword backward-delete-char backward-delete-bigword backward-delete-semiword backward-delete-viword backward-delete-emacsword delete-line forward-delete-line backward-delete-line kill-char kill-bigword kill-semiword kill-viword kill-emacsword backward-kill-char backward-kill-bigword backward-kill-semiword backward-kill-viword backward-kill-emacsword kill-line forward-kill-line backward-kill-line put-before put put-left put-pop undo undo-all cancel-undo cancel-undo-all redo complete complete-next-candidate complete-prev-candidate complete-next-column complete-prev-column complete-next-page complete-prev-page complete-list complete-all complete-max complete-max-then-list complete-max-then-next-candidate complete-max-then-prev-candidate clear-candidates vi-replace-char vi-insert-beginning vi-append vi-append-to-eol vi-replace vi-switch-case vi-switch-case-char vi-yank vi-yank-to-eol vi-delete vi-delete-to-eol vi-change vi-change-to-eol vi-change-line vi-yank-and-change vi-yank-and-change-to-eol vi-yank-and-change-line vi-substitute vi-append-last-bigword vi-exec-alias vi-edit-and-accept vi-complete-list vi-complete-all vi-complete-max vi-search-forward vi-search-backward emacs-transpose-chars emacs-transpose-words emacs-upcase-word emacs-downcase-word emacs-capitalize-word emacs-delete-horizontal-space emacs-just-one-space emacs-search-forward emacs-search-backward oldest-history newest-history return-history oldest-history-bol newest-history-bol return-history-bol oldest-history-eol newest-history-eol return-history-eol next-history prev-history next-history-bol prev-history-bol next-history-eol prev-history-eol srch-self-insert srch-backward-delete-char srch-backward-delete-line srch-continue-forward srch-continue-backward srch-accept-search srch-abort-search search-again search-again-rev search-again-forward search-again-backward beginning-search-forward beginning-search-backward __END__ testcase "$LINENO" -e 0 'printing commands' \ 4vi_insert <<\__END__ bindkey -v '\\' self-insert bindkey -v '\^V' expect-verbatim bindkey -v '\^J' accept-line bindkey -v '\^M' accept-line bindkey -v '\!' abort-line bindkey -v '\^C' abort-line bindkey -v '\#' eof-if-empty bindkey -v '\^D' eof-if-empty bindkey -v '\^[' setmode-vicommand bindkey -v '\^L' redraw-all bindkey -v '\R' forward-char bindkey -v '\L' backward-char bindkey -v '\H' beginning-of-line bindkey -v '\E' end-of-line bindkey -v '\X' delete-char bindkey -v '\B' backward-delete-char bindkey -v '\?' backward-delete-char bindkey -v '\^H' backward-delete-char bindkey -v '\^W' backward-delete-semiword bindkey -v '\$' backward-delete-line bindkey -v '\^U' backward-delete-line bindkey -v '\D' next-history-eol bindkey -v '\^N' next-history-eol bindkey -v '\U' prev-history-eol bindkey -v '\^P' prev-history-eol bindkey -v '\^I' complete-next-candidate bindkey -v '\bt' complete-prev-candidate __END__ testcase "$LINENO" 'printing default vi-insert bindings: output' \ 4vi_command <<\__END__ bindkey -a '\^[' noop bindkey -a 1 digit-argument bindkey -a 2 digit-argument bindkey -a 3 digit-argument bindkey -a 4 digit-argument bindkey -a 5 digit-argument bindkey -a 6 digit-argument bindkey -a 7 digit-argument bindkey -a 8 digit-argument bindkey -a 9 digit-argument bindkey -a 0 bol-or-digit bindkey -a '\^J' accept-line bindkey -a '\^M' accept-line bindkey -a '\!' abort-line bindkey -a '\^C' abort-line bindkey -a '\#' eof-if-empty bindkey -a '\^D' eof-if-empty bindkey -a '#' accept-with-hash bindkey -a i setmode-viinsert bindkey -a '\I' setmode-viinsert bindkey -a '\^L' redraw-all bindkey -a l forward-char bindkey -a ' ' forward-char bindkey -a '\R' forward-char bindkey -a h backward-char bindkey -a '\L' backward-char bindkey -a '\B' backward-char bindkey -a '\?' backward-char bindkey -a '\^H' backward-char bindkey -a W forward-bigword bindkey -a E end-of-bigword bindkey -a B backward-bigword bindkey -a w forward-viword bindkey -a e end-of-viword bindkey -a b backward-viword bindkey -a '\H' beginning-of-line bindkey -a '$' end-of-line bindkey -a '\E' end-of-line bindkey -a '|' go-to-column bindkey -a '^' first-nonblank bindkey -a f find-char bindkey -a F find-char-rev bindkey -a t till-char bindkey -a T till-char-rev bindkey -a ';' refind-char bindkey -a ',' refind-char-rev bindkey -a x kill-char bindkey -a '\X' kill-char bindkey -a X backward-kill-char bindkey -a P put-before bindkey -a p put bindkey -a u undo bindkey -a U undo-all bindkey -a '\^R' cancel-undo bindkey -a . redo bindkey -a r vi-replace-char bindkey -a I vi-insert-beginning bindkey -a a vi-append bindkey -a A vi-append-to-eol bindkey -a R vi-replace bindkey -a '~' vi-switch-case-char bindkey -a y vi-yank bindkey -a Y vi-yank-to-eol bindkey -a d vi-delete bindkey -a D forward-kill-line bindkey -a c vi-change bindkey -a C vi-change-to-eol bindkey -a S vi-change-line bindkey -a s vi-substitute bindkey -a _ vi-append-last-bigword bindkey -a '@' vi-exec-alias bindkey -a v vi-edit-and-accept bindkey -a '=' vi-complete-list bindkey -a '*' vi-complete-all bindkey -a '\\' vi-complete-max bindkey -a '?' vi-search-forward bindkey -a / vi-search-backward bindkey -a G oldest-history-bol bindkey -a g return-history-bol bindkey -a j next-history-bol bindkey -a '+' next-history-bol bindkey -a '\D' next-history-bol bindkey -a '\^N' next-history-bol bindkey -a k prev-history-bol bindkey -a -- - prev-history-bol bindkey -a '\U' prev-history-bol bindkey -a '\^P' prev-history-bol bindkey -a n search-again bindkey -a N search-again-rev __END__ testcase "$LINENO" 'printing default vi-command bindings: output' \ 4emacs <<\__END__ bindkey -e '\\' self-insert bindkey -e '\^[\^I' insert-tab bindkey -e '\^Q' expect-verbatim bindkey -e '\^V' expect-verbatim bindkey -e '\^[0' digit-argument bindkey -e '\^[1' digit-argument bindkey -e '\^[2' digit-argument bindkey -e '\^[3' digit-argument bindkey -e '\^[4' digit-argument bindkey -e '\^[5' digit-argument bindkey -e '\^[6' digit-argument bindkey -e '\^[7' digit-argument bindkey -e '\^[8' digit-argument bindkey -e '\^[9' digit-argument bindkey -e '\^[-' digit-argument bindkey -e '\^J' accept-line bindkey -e '\^M' accept-line bindkey -e '\!' abort-line bindkey -e '\^C' abort-line bindkey -e '\#' eof-or-delete bindkey -e '\^D' eof-or-delete bindkey -e '\^[#' accept-with-hash bindkey -e '\^L' redraw-all bindkey -e '\R' forward-char bindkey -e '\^F' forward-char bindkey -e '\L' backward-char bindkey -e '\^B' backward-char bindkey -e '\^[f' forward-emacsword bindkey -e '\^[F' forward-emacsword bindkey -e '\^[b' backward-emacsword bindkey -e '\^[B' backward-emacsword bindkey -e '\H' beginning-of-line bindkey -e '\^A' beginning-of-line bindkey -e '\E' end-of-line bindkey -e '\^E' end-of-line bindkey -e '\^]' find-char bindkey -e '\^[\^]' find-char-rev bindkey -e '\X' delete-char bindkey -e '\B' backward-delete-char bindkey -e '\?' backward-delete-char bindkey -e '\^H' backward-delete-char bindkey -e '\^[d' kill-emacsword bindkey -e '\^[D' kill-emacsword bindkey -e '\^W' backward-kill-bigword bindkey -e '\^[\B' backward-kill-emacsword bindkey -e '\^[\?' backward-kill-emacsword bindkey -e '\^[\^H' backward-kill-emacsword bindkey -e '\^K' forward-kill-line bindkey -e '\$' backward-kill-line bindkey -e '\^U' backward-kill-line bindkey -e '\^X\B' backward-kill-line bindkey -e '\^X\?' backward-kill-line bindkey -e '\^Y' put-left bindkey -e '\^[y' put-pop bindkey -e '\^[Y' put-pop bindkey -e '\^_' undo bindkey -e '\^X\$' undo bindkey -e '\^X\^U' undo bindkey -e '\^[\^R' undo-all bindkey -e '\^[r' undo-all bindkey -e '\^[R' undo-all bindkey -e '\^I' complete-next-candidate bindkey -e '\bt' complete-prev-candidate bindkey -e '\^[=' complete-list bindkey -e '\^[?' complete-list bindkey -e '\^[*' complete-all bindkey -e '\^T' emacs-transpose-chars bindkey -e '\^[t' emacs-transpose-words bindkey -e '\^[T' emacs-transpose-words bindkey -e '\^[l' emacs-downcase-word bindkey -e '\^[L' emacs-downcase-word bindkey -e '\^[u' emacs-upcase-word bindkey -e '\^[U' emacs-upcase-word bindkey -e '\^[c' emacs-capitalize-word bindkey -e '\^[C' emacs-capitalize-word bindkey -e '\^[\\' emacs-delete-horizontal-space bindkey -e '\^[ ' emacs-just-one-space bindkey -e '\^S' emacs-search-forward bindkey -e '\^R' emacs-search-backward bindkey -e '\^[<' oldest-history-eol bindkey -e '\^[>' return-history-eol bindkey -e '\D' next-history-eol bindkey -e '\^N' next-history-eol bindkey -e '\U' prev-history-eol bindkey -e '\^P' prev-history-eol __END__ testcase "$LINENO" 'printing default emacs bindings: output' \ 4vi_insert_x test_OE -e 0 'removing all default vi-insert bindings' . ./vi_insert_x bindkey -v __IN__ testcase "$LINENO" 'restoring bindings from previous output' \ 4&1 | head -n 1 __IN__ bindkey: option `--vi-' is ambiguous __OUT__ #` test_Oe -e 2 'missing argument' bindkey __IN__ bindkey: no option is specified __ERR__ test_Oe -e 2 'too many operands with -a' bindkey -a X Y Z __IN__ bindkey: too many operands are specified __ERR__ test_Oe -e 2 'operand with -l' bindkey -l X __IN__ bindkey: no operand is expected __ERR__ test_O -d -e 1 'printing to closed stream (-l)' bindkey -l >&- __IN__ test_O -d -e 1 'printing to closed stream (-a)' bindkey -a >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/brace-y.tst000066400000000000000000000043011354143602500156350ustar00rootroot00000000000000# brace-y.tst: yash-specific test of brace expansion setup -d ( setup 'set -o braceexpand' test_oE 'simple brace expansion' bracket {1,22,333} bracket a{1,2}b __IN__ [1][22][333] [a1b][a2b] __OUT__ test_oE 'joined brace expansions' bracket {aa,b}{1,22,333} bracket a{1,2}b{3,4}c __IN__ [aa1][aa22][aa333][b1][b22][b333] [a1b3c][a1b4c][a2b3c][a2b4c] __OUT__ test_oE 'empty elements' bracket a{1,,2}b bracket x{,,} bracket {,,} __IN__ [a1b][ab][a2b] [x][x][x] [][][] __OUT__ test_oE 'nested brace expansions' bracket a{p,q{1,2}r}b __IN__ [apb][aq1rb][aq2rb] __OUT__ test_oE 'quoted elements' bracket \a{\1,"2",'3'}'' bracket a{\{,\,,\}} bracket a{\\,\',\"} __IN__ [a1][a2][a3] [a{][a,][a}] [a\][a'][a"] __OUT__ #' test_oE 'simple sequences' bracket {1..3} bracket a{1..3}b bracket {1..1} bracket {-3..2} __IN__ [1][2][3] [a1b][a2b][a3b] [1] [-3][-2][-1][0][1][2] __OUT__ test_oE 'multi-digit sequences' bracket {99..101} bracket {099..101} bracket {-101..-99} bracket {-101..-099} __IN__ [99][100][101] [099][100][101] [-101][-100][-99] [-101][-100][-099] __OUT__ test_oE 'sequences with non-default steps' bracket {2..11..3}x bracket {2..11..-3}y bracket {0..5..2} __IN__ [2x][5x][8x][11x] [2y] [0][2][4] __OUT__ test_oE 'descending sequences' bracket {2..-2}x bracket {-2..-11..-3}y bracket {-2..-11..3}z bracket {0..-5..-2} __IN__ [2x][1x][0x][-1x][-2x] [-2y][-5y][-8y][-11y] [-2z] [0][-2][-4] __OUT__ test_oE 'invalid step' bracket {1..5..0} {0..0..0} __IN__ [{1..5..0}][{0..0..0}] __OUT__ test_oE 'not brace expansions' bracket { } {a,b a,b} bracket a{b}c a\{b,c}d a{b\,c}d a{b,c\}d a'{'b,c}d a{b,c"}"d bracket a{1..}b a{..1}b {1.9} bracket \{1..2} {\1..2} {1\..2} {1..\2} {1..2\} __IN__ [{][}][{a,b][a,b}] [a{b}c][a{b,c}d][a{b,c}d][a{b,c}d][a{b,c}d][a{b,c}d] [a{1..}b][a{..1}b][{1.9}] [{1..2}][{1..2}][{1..2}][{1..2}][{1..2}] __OUT__ test_oE 'result of tilde expansion is not subject to brace expansion' HOME=/{1,2} bracket ~ __IN__ [/{1,2}] __OUT__ test_oE 'result of parameter expansion is not subject to brace expansion' a='{1,2}' bracket $a __IN__ [{1,2}] __OUT__ ) test_oE 'disabled brace expansion' bracket {{aa,b}{1,22,333}}{1..9} __IN__ [{{aa,b}{1,22,333}}{1..9}] __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/bracket-y.tst000066400000000000000000000155521354143602500162060ustar00rootroot00000000000000# bracket-y.tst: yash-specific test of the double-bracket command if ! testee -c 'command -v [[' >/dev/null; then skip="true" fi test_OE -e 1 'single empty string primary' [[ '' ]] __IN__ test_OE -e 0 'single normal string primary' [[ foo ]] __IN__ # Note: zsh rejects -# and -- test_OE -e 0 'single dash-starting string primary' [[ -# ]] && [[ -- ]] && [[ -no-such-operator ]] __IN__ test_OE -e 0 'single quoted-operator string primary' [[ -\f ]] __IN__ test_OE -e 0 'single string primary expanding to operator' op=-f [[ $op ]] __IN__ test_OE -e 0 'single unary primary -d' [[ -d / ]] && ! [[ -d /dev/null ]] __IN__ test_OE -e 0 'single unary primary -n' [[ -n -n ]] && ! [[ -n """" ]] __IN__ test_OE -e 0 'single unary primary with operator-looking operand' [[ -n -eq ]] && [[ -n ! ]] __IN__ test_OE -e 0 'single binary primary -eq' [[ 0 -eq 0 ]] && ! [[ -1 -eq 1 ]] __IN__ # Note: version number comparing primaries are unique to yash test_OE -e 0 'single binary primary -vlt' [[ 0.2.3 -vlt 0.10 ]] && ! [[ 01.2 -vlt 1.2 ]] __IN__ test_OE -e 0 'single binary primary <' [[ 0 < 1 ]] && ! [[ a < a ]] __IN__ test_OE -e 0 'single binary primary >' [[ 14.0 > 13.0 ]] && ! [[ foo > foo ]] __IN__ test_OE -e 0 'literal pattern matching with binary primary =' [[ foobar = f*b?r ]] && ! [[ foobar = "f*b?r" ]] __IN__ # Note: zsh by default does not match expanded patterns test_OE -e 0 'expanded pattern matching with binary primary =' lhs='foobar' rhs='f*b?r' [[ "$lhs" = $rhs ]] && ! [[ "$lhs" = "$rhs" ]] __IN__ test_OE -e 0 'literal pattern matching with binary primary ==' [[ foobar == f*b?r ]] && ! [[ foobar == "f*b?r" ]] __IN__ test_OE -e 0 'expanded pattern matching with binary primary ==' lhs='foobar' rhs='f*b?r' [[ "$lhs" == $rhs ]] && ! [[ "$lhs" == "$rhs" ]] __IN__ test_OE -e 0 'literal pattern matching with binary primary !=' ! [[ foobar != f*b?r ]] && [[ foobar != "f*b?r" ]] __IN__ test_OE -e 0 'expanded pattern matching with binary primary !=' lhs='foobar' rhs='f*b?r' ! [[ "$lhs" != $rhs ]] && [[ "$lhs" != "$rhs" ]] __IN__ # Note: mksh does not support regex test_OE -e 0 'literal regex matching with binary primary =~' [[ abc123xyz =~ c[[:digit:]]*x ]] && ! [[ abc =~ b'*' ]] __IN__ test_OE -e 0 'expanded pattern matching with binary primary =~' lhs='abc123xyz' rhs='c[[:digit:]]*x' [[ "$lhs" =~ $rhs ]] && ! [[ "$lhs" =~ "$rhs" ]] __IN__ # Note: bash returns exit status of 2 and zsh prints an error message test_OE -e 1 'ill-formed regex with binary primary =~' [[ foo =~ * ]] __IN__ test_OE -e 0 'single binary primary with operator-looking operand' [[ -eq = -eq ]] && [[ \-f = -f ]] && [[ ''= = = ]] && [[ \! = ! ]] __IN__ test_OE -e 0 'parentheses' [[ ( foo ) ]] && [[ ( -n -n ) ]] && [[ ( 100 -eq 100 ) ]] __IN__ test_OE -e 0 'negating empty string primary' [[ ! '' ]] __IN__ test_OE -e 1 'negating non-empty string primary' [[ ! foo ]] __IN__ test_OE -e 0 'negating unary primary (false -> true)' [[ ! -n '' ]] __IN__ test_OE -e 1 'negating unary primary (true -> false)' [[ ! -n foo ]] __IN__ test_OE -e 0 'negating binary primary (false -> true)' [[ ! a = b ]] __IN__ test_OE -e 1 'negating binary primary (true -> false)' [[ ! a = a ]] __IN__ test_OE -e 0 'true && true' [[ foo && bar = bar ]] __IN__ test_OE -e 1 'true && false' [[ foo && bar != bar ]] __IN__ test_OE -e 1 'false && ...' [[ foo != foo && $(echo not reached >&2) ]] __IN__ test_OE -e 0 'true || ...' [[ foo = foo || $(echo not reached >&2) ]] __IN__ test_OE -e 0 'false || true' [[ foo != foo || -n foo ]] __IN__ test_OE -e 1 'false || false' [[ foo != foo || '' ]] __IN__ test_OE -e 1 '! has higher precedence than &&' [[ ! -z foo && foo != foo ]] __IN__ test_OE -e 0 '! has higher precedence than ||' [[ ! -n foo || foo = foo ]] __IN__ test_OE -e 0 '&& has higher precedence to the left of ||' [[ -z foo && 0 -eq 0 || bar = bar ]] __IN__ test_OE -e 0 '&& has higher precedence to the left of ||' [[ foo = foo || 1 -eq 1 && -z bar ]] __IN__ # Note: other shells (bash, ksh, mksh and zsh) don't accept this test_OE -e 0 'IO_NUMBER is not special between [[ and ]]' [[ 0<1 ]] __IN__ test_Oe -e 2 'expansion error' unset v eval '[[ $(echo ok >&2) || ${v?!!!} || $(echo not reached >&2) ]]' echo not reached __IN__ ok eval: v: !!! __ERR__ test_Oe -e 2 'syntax error: empty [[ ]]' [[ ]] __IN__ syntax error: conditional expression is missing or incomplete between `[[' and `]]' __ERR__ #' #` #' #` # Note: ksh and mksh accept this which bash and zsh don't test_Oe -e 2 'syntax error: single unquoted ]]' [[ ]] ]] __IN__ syntax error: conditional expression is missing or incomplete between `[[' and `]]' __ERR__ #' #` #' #` test_Oe -e 2 'syntax error: newline after [[' [[ __IN__ syntax error: unexpected linebreak in the middle of the [[ ... ]] command __ERR__ test_Oe -e 2 'syntax error: newline in [[ ]]' [[ foo __IN__ syntax error: `]]' is missing __ERR__ #' #` test_Oe -e 2 'syntax error: missing operand for unary primary' [[ -f ]] __IN__ syntax error: conditional expression is missing or incomplete between `[[' and `]]' __ERR__ #' #` #' #` test_Oe -e 2 'syntax error: missing right-hand-side operand for binary primary' [[ foo = ]] __IN__ syntax error: conditional expression is missing or incomplete between `[[' and `]]' __ERR__ #' #` #' #` test_Oe -e 2 'syntax error: redundant word' [[ foo bar ]] __IN__ syntax error: invalid word `bar' between `[[' and `]]' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'syntax error: -a is not binary primary' [[ foo -a bar ]] __IN__ syntax error: invalid word `-a' between `[[' and `]]' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'syntax error: -o is not binary primary' [[ foo -o bar ]] __IN__ syntax error: invalid word `-o' between `[[' and `]]' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'syntax error: invalid operator' [[ ; ]] __IN__ syntax error: `;' is not a valid operand in the conditional expression __ERR__ #' #` test_Oe -e 2 'syntax error: newline after ( in [[ ]]' [[ ( __IN__ syntax error: unexpected linebreak in the middle of the [[ ... ]] command __ERR__ test_Oe -e 2 'syntax error: newline after string primary in ( in [[ ]]' [[ ( foo __IN__ syntax error: `)' is missing __ERR__ #' #` test_Oe -e 2 'syntax error: missing ) before ]]' [[ ( foo ]] __IN__ syntax error: `)' is missing __ERR__ #' #` test_Oe -e 2 'syntax error: empty ( ) in [[ ]]' [[ ( ) ]] __IN__ syntax error: `)' is not a valid operand in the conditional expression __ERR__ #' #` test_oE -e 0 '[[ is keyword' command -v [[ command -V [[ __IN__ [[ [[: a shell keyword __OUT__ # Note: ]] is keyword in bash test_Oe -e 1 ']] is not keyword' PATH= command -V ]] __IN__ command: no such command `]]' __ERR__ #' #` ( posix="true" test_oE -e 0 '[[ is keyword (POSIX)' command -v [[ command -V [[ __IN__ [[ [[: a shell keyword __OUT__ test_Oe -e 2 'syntax error: [[ is not supported in POSIX mode' [[ foo ]] __IN__ syntax error: The [[ ... ]] syntax is not supported in the POSIXly-correct mode __ERR__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/break-p.tst000066400000000000000000000146671354143602500156540ustar00rootroot00000000000000# break-p.tst: test of the break built-in for any POSIX-compliant shell posix="true" test_oE 'breaking one for loop, unnested' for i in 1 2 3; do echo in $i break 1 echo out $i done echo done $? __IN__ in 1 done 0 __OUT__ test_oE 'breaking one while loop, unnested' while true; do echo in break 1 echo out done echo done $? __IN__ in done 0 __OUT__ test_oE 'breaking one until loop, unnested' until false; do echo in break 1 echo out done echo done $? __IN__ in done 0 __OUT__ test_oE 'breaking one for loop, nested in for loop' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j break 1 echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a out 1 in 2 in 2 a out 2 in 3 in 3 a out 3 done 0 __OUT__ test_oE 'breaking one for loop, nested in while loop' i=1 while [ $i -le 3 ]; do echo in $i for j in a b c; do echo in $i $j break 1 echo out $i $j done echo out $i i=$((i+1)) done echo done $? __IN__ in 1 in 1 a out 1 in 2 in 2 a out 2 in 3 in 3 a out 3 done 0 __OUT__ test_oE 'breaking one while loop, nested in while loop' i=1 while [ $i -le 3 ]; do echo in outer $i while true; do echo in inner $i break 1 echo out inner $i done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 out outer 1 in outer 2 in inner 2 out outer 2 in outer 3 in inner 3 out outer 3 done 0 __OUT__ test_oE 'breaking one while loop, nested in until loop' i=1 until [ $i -gt 3 ]; do echo in outer $i while true; do echo in inner $i break 1 echo out inner $i done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 out outer 1 in outer 2 in inner 2 out outer 2 in outer 3 in inner 3 out outer 3 done 0 __OUT__ test_oE 'breaking one until loop, nested in until loop' i=1 until [ $i -gt 3 ]; do echo in outer $i until false; do echo in inner $i break 1 echo out inner $i done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 out outer 1 in outer 2 in inner 2 out outer 2 in outer 3 in inner 3 out outer 3 done 0 __OUT__ test_oE 'breaking one until loop in function, nested in until loop' func() { until false; do echo in inner $i break 1 echo out inner $i done echo out func } i=1 until [ $i -gt 2 ]; do echo in outer $i func echo out outer $i i=$((i+1)) done __IN__ in outer 1 in inner 1 out func out outer 1 in outer 2 in inner 2 out func out outer 2 __OUT__ test_oE 'breaking two for loops, outermost' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j break 2 echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a done 0 __OUT__ test_oE 'breaking for and while loops, outermost' i=1 while [ $i -le 3 ]; do echo in $i for j in a b c; do echo in $i $j break 2 echo out $i $j done echo out $i i=$((i+1)) done echo done $? __IN__ in 1 in 1 a done 0 __OUT__ test_oE 'breaking two while loops, outermost' i=1 while [ $i -le 3 ]; do echo in outer $i while true; do echo in inner $i break 2 echo out inner $i done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 done 0 __OUT__ test_oE 'breaking while and until loops, outermost' i=1 until [ $i -gt 3 ]; do echo in outer $i while true; do echo in inner $i break 2 echo out inner $i done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 done 0 __OUT__ test_oE 'breaking two for loops, nested in another for loop' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j for k in + -; do echo in $i $j $k break 2 echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + out 1 in 2 in 2 a in 2 a + out 2 in 3 in 3 a in 3 a + out 3 done 0 __OUT__ test_oE 'breaking three for loops, outermost' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j for k in + -; do echo in $i $j $k break 3 echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + done 0 __OUT__ test_oE 'default operand is 1' for i in 1; do echo in $i for j in a; do echo in $i $j for k in +; do echo in $i $j $k break echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + out 1 a out 1 done 0 __OUT__ test_OE -e 0 'exit status of break with $? > 0' for i in 1; do false break done __IN__ test_O -d -e n 'zero operand' for i in 1; do break 0 done __IN__ test_OE 'breaking one more than actual nest level one' for i in 1; do break 2 echo not reached done __IN__ test_OE 'breaking one more than actual nest level two' for i in 1; do for j in a; do break 3 echo not reached 1 done echo not reached 2 done __IN__ test_OE 'breaking much more than actual nest level one' for i in 1; do break 100 echo not reached done __IN__ # This is a questionable case. Is this really a "lexically enclosing" loop as # defined in POSIX? Most shells (other than mksh) support this case. test_OE 'breaking out of eval' for i in 1; do eval break echo not reached done __IN__ test_OE 'breaking with !' for i in 1; do ! break echo not reached done __IN__ test_OE 'breaking before &&' for i in 1; do break && echo not reached 1 echo not reached 2 $? done __IN__ test_OE 'breaking after &&' for i in 1; do true && break echo not reached $? done __IN__ test_OE 'breaking before ||' for i in 1; do break || echo not reached 1 echo not reached 2 $? done __IN__ test_OE 'breaking after ||' for i in 1; do false || break echo not reached $? done __IN__ test_OE 'breaking out of brace' for i in 1; do { break; } echo not reached done __IN__ test_OE 'breaking out of if' for i in 1; do if break; then echo not reached then; else echo not reached else; fi echo not reached done __IN__ test_OE 'breaking out of then' for i in 1; do if true; then break; echo not reached then; else not reached else; fi echo not reached done __IN__ test_OE 'breaking out of else' for i in 1; do if false; then echo not reached then; else break; not reached else; fi echo not reached done __IN__ test_OE 'breaking out of case' for i in 1; do case x in x) break echo not reached in case esac echo not reached after esac done __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/break-y.tst000066400000000000000000000046501354143602500156540ustar00rootroot00000000000000# break-y.tst: yash-specific test of the break built-in echo 'break; echo \$?=$?' >break test_oe 'breaking out of dot' for i in 1 2; do echo $i . ./break done __IN__ 1 $?=2 2 $?=2 __OUT__ break: not in a loop break: not in a loop __ERR__ test_oe 'breaking out of function' b() { break; echo \$?=$?; } for i in 1 2; do echo $i b done __IN__ 1 $?=2 2 $?=2 __OUT__ break: not in a loop break: not in a loop __ERR__ test_oe 'breaking out of subshell' for i in 1; do (break) || echo ok done __IN__ ok __OUT__ break: not in a loop __ERR__ test_oe 'breaking out of trap' trap 'break || echo trapped' USR1 for i in 1; do kill -USR1 $$ echo ok done __IN__ trapped ok __OUT__ break: not in a loop __ERR__ test_oE 'breaking iteration, unnested, short option' eval -i 'echo 1' \ '(exit 13); break -i; echo not reached 1' \ 'echo not reached 2' echo $? __IN__ 1 13 __OUT__ test_oE 'breaking iteration, unnested, long option' eval -i 'echo 1' \ '(exit 13); break --iteration; echo not reached 1' \ 'echo not reached 2' echo $? __IN__ 1 13 __OUT__ test_oE 'breaking nested iteration' eval -i 'eval -i "break -i" "echo not reached"; echo broke' __IN__ broke __OUT__ test_OE 'breaking loop out of iteration' for i in 1; do eval -i break 'echo not reached 1' echo not reached 2 done __IN__ test_o 'breaking loop out of auxiliary not allowed' COMMAND_NOT_FOUND_HANDLER=(break 'echo reached 1 $?') for i in 1; do ./_no_such_command_ echo reached 2 $? done __IN__ reached 1 0 reached 2 127 __OUT__ test_OE 'breaking iteration out of eval' eval -i 'eval "break -i"; echo not reached 1' 'echo not reached 2' __IN__ echo 'break -i' >break-i test_OE 'breaking iteration out of dot' eval -i '. ./break-i; echo not reached 1' 'echo not reached 2' __IN__ test_OE 'breaking iteration out of loop' eval -i 'for i in 1; do break -i; done; echo not reached 1' \ 'echo not reached 2' __IN__ test_Oe -e n 'breaking without target loop' break __IN__ break: not in a loop __ERR__ test_Oe -e n 'breaking without target iteration' break -i __IN__ break: not in an iteration __ERR__ test_Oe -e n 'too many operands' break 1 2 __IN__ break: too many operands are specified __ERR__ test_Oe -e n 'operand and -i' break -i 1 __IN__ break: no operand is expected __ERR__ test_Oe -e n 'invalid option' break --no-such-option __IN__ break: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/builtins-p.tst000066400000000000000000000176351354143602500164170ustar00rootroot00000000000000# builtins-p.tst: test of built-ins' attributes for any POSIX-compliant shell posix="true" ##### Special built-ins test_o 'assignment on special built-in colon is persistent' a=a a=b : echo $a __IN__ b __OUT__ test_o 'assignment on special built-in dot is persistent' a=a a=b . /dev/null echo $a __IN__ b __OUT__ test_o 'assignment on special built-in break is persistent' a=a for i in 1; do a=b break done echo $a __IN__ b __OUT__ test_o 'assignment on special built-in continue is persistent' a=a for i in 1; do a=b continue done echo $a __IN__ b __OUT__ test_o 'assignment on special built-in eval is persistent' a=a a=b eval '' echo $a __IN__ b __OUT__ test_o 'assignment on special built-in exec is persistent' a=a a=b exec echo $a __IN__ b __OUT__ #test_o 'assignment on special built-in exit is persistent' test_o 'assignment on special built-in export is persistent' a=a a=b export c=c echo $a __IN__ b __OUT__ test_o 'assignment on special built-in readonly is persistent' a=a a=b readonly c=c echo $a __IN__ b __OUT__ test_o 'assignment on special built-in return is persistent' f() { a=b return; } a=a f echo $a __IN__ b __OUT__ test_o 'assignment on special built-in set is persistent' a=a a=b set '' echo $a __IN__ b __OUT__ test_o 'assignment on special built-in shift is persistent' a=a a=b shift 0 echo $a __IN__ b __OUT__ test_o 'assignment on special built-in times is persistent' a=a a=b times >/dev/null echo $a __IN__ b __OUT__ test_o 'assignment on special built-in trap is persistent' a=a a=b trap - TERM echo $a __IN__ b __OUT__ test_o 'assignment on special built-in unset is persistent' a=a a=b unset b echo $a __IN__ b __OUT__ test_O 'function cannot override special built-in colon' :() { echo not reached; } : __IN__ test_O 'function cannot override special built-in dot' .() { echo not reached; } . /dev/null __IN__ test_OE 'function cannot override special built-in break' break() { echo not reached; } for i in 1; do break done __IN__ test_OE 'function cannot override special built-in continue' continue() { echo not reached; } for i in 1; do continue done __IN__ test_OE 'function cannot override special built-in eval' eval() { echo not reached; } eval '' __IN__ test_OE 'function cannot override special built-in exec' exec() { echo not reached; } exec __IN__ test_OE 'function cannot override special built-in exit' exit() { echo not reached; } exit __IN__ test_OE 'function cannot override special built-in export' export() { echo not reached; } export a=a __IN__ test_OE 'function cannot override special built-in readonly' readonly() { echo not reached; } readonly a=a __IN__ test_OE 'function cannot override special built-in return' return() { echo not reached; } fn() { return; } fn __IN__ test_OE 'function cannot override special built-in set' set() { echo not reached; } set '' __IN__ test_OE 'function cannot override special built-in shift' shift() { echo not reached; } shift 0 __IN__ test_E 'function cannot override special built-in times' times() { echo not reached >&2; } times __IN__ test_OE 'function cannot override special built-in trap' trap() { echo not reached; } trap - TERM __IN__ test_OE 'function cannot override special built-in unset' unset() { echo not reached; } unset unset __IN__ # $1 = line no. # $2 = command name (other than special built-ins) test_nonspecial_builtin_function_override() { testcase "$1" "function overrides non-special command $2" \ 3<<__IN__ 4<<__OUT__ $2() { echo function overrides $2; } $2 XXX __IN__ function overrides $2 __OUT__ } test_nonspecial_builtin_function_override "$LINENO" alias test_nonspecial_builtin_function_override "$LINENO" bg test_nonspecial_builtin_function_override "$LINENO" cd test_nonspecial_builtin_function_override "$LINENO" command test_nonspecial_builtin_function_override "$LINENO" false test_nonspecial_builtin_function_override "$LINENO" fc test_nonspecial_builtin_function_override "$LINENO" fg test_nonspecial_builtin_function_override "$LINENO" getopts test_nonspecial_builtin_function_override "$LINENO" hash test_nonspecial_builtin_function_override "$LINENO" jobs test_nonspecial_builtin_function_override "$LINENO" kill test_nonspecial_builtin_function_override "$LINENO" newgrp test_nonspecial_builtin_function_override "$LINENO" pwd test_nonspecial_builtin_function_override "$LINENO" read test_nonspecial_builtin_function_override "$LINENO" true test_nonspecial_builtin_function_override "$LINENO" type test_nonspecial_builtin_function_override "$LINENO" ulimit test_nonspecial_builtin_function_override "$LINENO" umask test_nonspecial_builtin_function_override "$LINENO" unalias test_nonspecial_builtin_function_override "$LINENO" wait test_nonspecial_builtin_function_override "$LINENO" grep test_nonspecial_builtin_function_override "$LINENO" sed ( setup 'PATH=; unset PATH' test_OE -e 0 'special built-in colon can be invoked without $PATH' : __IN__ test_OE -e 0 'special built-in dot can be invoked without $PATH' . /dev/null __IN__ test_OE -e 0 'special built-in break can be invoked without $PATH' for i in 1; do break done __IN__ test_OE -e 0 'special built-in continue can be invoked without $PATH' for i in 1; do continue done __IN__ test_OE -e 0 'special built-in eval can be invoked without $PATH' eval '' __IN__ test_OE -e 0 'special built-in exec can be invoked without $PATH' exec __IN__ test_OE -e 0 'special built-in exit can be invoked without $PATH' exit __IN__ test_OE -e 0 'special built-in export can be invoked without $PATH' export a=a __IN__ test_OE -e 0 'special built-in readonly can be invoked without $PATH' readonly a=a __IN__ test_OE -e 0 'special built-in return can be invoked without $PATH' fn() { return; } fn __IN__ test_OE -e 0 'special built-in set can be invoked without $PATH' set '' __IN__ test_OE -e 0 'special built-in shift can be invoked without $PATH' shift 0 __IN__ test_E -e 0 'special built-in times can be invoked without $PATH' times __IN__ test_OE -e 0 'special built-in trap can be invoked without $PATH' trap - TERM __IN__ test_OE -e 0 'special built-in unset can be invoked without $PATH' unset unset __IN__ ) ##### Intrinsic built-ins ( setup 'PATH=; unset PATH' test_OE -e 0 'intrinsic built-in alias can be invoked without $PATH' alias a=a __IN__ # Tested in builtins-y.tst. #test_OE -e 0 'intrinsic built-in bg can be invoked without $PATH' test_OE -e 0 'intrinsic built-in cd can be invoked without $PATH' cd . __IN__ test_OE -e 0 'intrinsic built-in command can be invoked without $PATH' command : __IN__ test_OE 'intrinsic built-in false can be invoked without $PATH' false __IN__ # Tested in builtins-y.tst. #test_OE -e 0 'intrinsic built-in fc can be invoked without $PATH' #test_OE -e 0 'intrinsic built-in fg can be invoked without $PATH' test_OE -e 0 'intrinsic built-in getopts can be invoked without $PATH' getopts o o -o __IN__ test_OE -e 0 'intrinsic built-in hash can be invoked without $PATH' hash -r __IN__ test_OE -e 0 'intrinsic built-in jobs can be invoked without $PATH' jobs __IN__ test_OE -e 0 'intrinsic built-in kill can be invoked without $PATH' kill -0 $$ __IN__ # Many shells including yash does not implement newgrp as a built-in. #TODO: test_OE -e 0 'intrinsic built-in newgrp can be invoked without $PATH' test_E -e 0 'intrinsic built-in pwd can be invoked without $PATH' pwd __IN__ test_OE -e 0 'intrinsic built-in read can be invoked without $PATH' read a _this_line_is_read_by_the_read_built_in_ __IN__ test_OE -e 0 'intrinsic built-in true can be invoked without $PATH' true __IN__ test_E -e 0 'intrinsic built-in type can be invoked without $PATH' type type __IN__ # Tested in builtins-y.tst. #test_E -e 0 'intrinsic built-in ulimit can be invoked without $PATH' test_OE -e 0 'intrinsic built-in umask can be invoked without $PATH' umask 000 __IN__ test_OE -e 0 'intrinsic built-in unalias can be invoked without $PATH' unalias -a __IN__ test_OE -e 0 'intrinsic built-in wait can be invoked without $PATH' wait __IN__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/builtins-y.tst000066400000000000000000000013501354143602500164130ustar00rootroot00000000000000# builtins-y.tst: yash-specific test of built-ins' attributes ../checkfg || skip="true" # %REQUIRETTY% ##### Intrinsic built-ins if testee -c 'command -bv ulimit' >/dev/null; then has_ulimit=true else has_ulimit=false fi ( posix="true" setup 'PATH=; unset PATH' test_OE -e 0 'intrinsic built-in bg can be invoked without $PATH' -em "$TESTEE" -c 'kill -STOP $$'& bg >/dev/null wait __IN__ test_OE -e 0 'intrinsic built-in fg can be invoked without $PATH' -em :& fg >/dev/null __IN__ #TODO: test_OE -e 0 'intrinsic built-in fc can be invoked without $PATH' ( if ! $has_ulimit; then skip="true" fi test_E -e 0 'intrinsic built-in ulimit can be invoked without $PATH' ulimit __IN__ ) ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/case-p.tst000066400000000000000000000134721354143602500154740ustar00rootroot00000000000000# case-p.tst: test of case command for any POSIX-compliant shell posix="true" test_oE 'case word is subject to tilde expansion' HOME=/home case ~/foo in /home/foo) echo matched;; esac __IN__ matched __OUT__ test_oE 'case word is subject to parameter expansion' HOME=/home case $HOME/foo in /home/foo) echo matched;; esac __IN__ matched __OUT__ test_oE 'case word is subject to command substitution' case $(echo foo)`echo bar` in foobar) echo matched;; esac __IN__ matched __OUT__ test_oE 'case word is subject to arithmetic expansion' case $((1+2)) in 3) echo matched;; esac __IN__ matched __OUT__ test_oE 'case word is subject to quote removal' w='"1"'"'2'"\3 case '"1"'"'2'"\3 in $w) echo matched;; esac __IN__ matched __OUT__ test_oE 'case pattern is subject to tilde expansion' HOME=/home case /home/foo in ~/foo) echo matched;; esac __IN__ matched __OUT__ test_oE 'case pattern is subject to parameter expansion' HOME=/home case /home/foo in $HOME/foo) echo matched;; esac __IN__ matched __OUT__ test_oE 'case pattern is subject to command substitution' case foobar in $(echo foo)`echo bar`) echo matched;; esac __IN__ matched __OUT__ test_oE 'case pattern is subject to arithmetic expansion' case 3 in $((1+2))) echo matched;; esac __IN__ matched __OUT__ test_oE 'case pattern is subject to quote removal' w='"1"'"'2'"\3 case $w in '"1"'"'2'"\3) echo matched;; esac __IN__ matched __OUT__ test_oE 'pattern matching and quotes (*)' case '*ab' in \*\*\*) echo not reached;; '***') echo not reached;; "***") echo not reached;; \**) echo matched;; esac __IN__ matched __OUT__ test_oE 'pattern matching and quotes (?)' case '?a' in \?\?) echo not reached;; '??') echo not reached;; "??") echo not reached;; \??) echo matched;; esac __IN__ matched __OUT__ test_oE 'pattern matching and quotes ([])' case '[a' in \[\[abc]) echo not reached;; '[['abc]) echo not reached;; "[["abc]) echo not reached;; \[[abc]) echo matched;; esac __IN__ matched __OUT__ test_oE '* and ? match / and .' case //-/-.-. in *-?-*-?) echo matched;; esac __IN__ matched __OUT__ test_oe 'patterns are not expanded after first match' case 1 in $(echo expanded 0 >&2; echo 0)) echo matched 0;; $(echo expanded 1 >&2; echo 1)) echo matched 1;; $(echo expanded 2 >&2; echo 2)) echo matched 2;; esac __IN__ matched 1 __OUT__ expanded 0 expanded 1 __ERR__ test_oE 'multiple patterns for single command list' case 1 in a|b|c) echo not reached;; 0 | 1 | 2 ) echo matched;; esac __IN__ matched __OUT__ test_OE -e 0 'exit status of case command (unmatched, empty)' false case $(false) in esac __IN__ test_OE -e 0 'exit status of case command (unmatched, non-empty)' case $(false) in 1) true; (exit 11);; 2) true; (exit 17);; 3) true; (exit 19);; esac __IN__ # The behavior is POSIXly-unspecified for this case. See case-y.tst. #test_OE -e 0 'exit status of case command (matched, empty)' test_OE -e 17 'exit status of case command (matched, non-empty)' case $(echo 2; exit 2) in 1) true; (exit 11);; 2) true; (exit 17);; 3) true; (exit 19);; esac __IN__ test_oE 'patterns can be preceded by (' case a in (a) echo matched 1;; (b) echo not reached 1 b;; (c) echo not reached 1 c;; esac case a in a) echo matched 2;; (b) echo not reached 2 b;; c) echo not reached 2 c;; (d) echo not reached 2 d;; esac __IN__ matched 1 matched 2 __OUT__ test_oE 'linebreak after word' case foo in foo)echo matched;;esac __IN__ matched __OUT__ test_oE 'linebreak after in' case foo in foo)echo matched;;esac __IN__ matched __OUT__ test_oE 'linebreak after )' case foo in foo) echo matched;;esac __IN__ matched __OUT__ test_oE 'linebreak before ;;' case foo in foo)echo matched ;;esac __IN__ matched __OUT__ test_oE '; before ;;' case foo in foo)echo matched; ;;esac __IN__ matched __OUT__ test_oE '& before ;;' case foo in foo)echo matched&;;esac wait __IN__ matched __OUT__ test_oE 'linebreak after ;;' case foo in bar)echo not reached;; foo)echo matched;;esac __IN__ matched __OUT__ test_oE 'linebreak before esac' case foo in foo)echo matched;; esac __IN__ matched __OUT__ test_oE ';; can be omitted before esac' case 2 in 0) echo a;; 1) echo b;; 2) echo c esac case 2 in 0) echo A;; 1) echo B;; 2) echo C; esac case 2 in 0) echo A;; 1) echo B;; *) esac case 2 in 0) echo A;; 1) echo B;; *) esac __IN__ c C __OUT__ # $1 = LINENO # $2 = reserved word test_reserved_word_as_pattern() { testcase "$1" "reserved word $2 as pattern" 3<<__IN__ 4<<\__OUT__ case $2 in $2) echo matched;; esac __IN__ matched __OUT__ } test_reserved_word_as_pattern "$LINENO" ! test_reserved_word_as_pattern "$LINENO" { test_reserved_word_as_pattern "$LINENO" } test_reserved_word_as_pattern "$LINENO" [[ test_reserved_word_as_pattern "$LINENO" ]] test_reserved_word_as_pattern "$LINENO" case test_reserved_word_as_pattern "$LINENO" do test_reserved_word_as_pattern "$LINENO" done test_reserved_word_as_pattern "$LINENO" elif test_reserved_word_as_pattern "$LINENO" else #test_reserved_word_as_pattern "$LINENO" esac test_reserved_word_as_pattern "$LINENO" fi test_reserved_word_as_pattern "$LINENO" for test_reserved_word_as_pattern "$LINENO" function test_reserved_word_as_pattern "$LINENO" if test_reserved_word_as_pattern "$LINENO" in test_reserved_word_as_pattern "$LINENO" select test_reserved_word_as_pattern "$LINENO" then test_reserved_word_as_pattern "$LINENO" until test_reserved_word_as_pattern "$LINENO" while test_oE 'reserved word esac as (non-first) pattern' case esac in -|esac) echo matched;; esac __IN__ matched __OUT__ test_oE 'redirection on case command' case $(echo foo >&2) in $(echo bar >&2)) echo baz >&2;; esac 2>redir_out cat redir_out __IN__ foo bar baz __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/case-y.tst000066400000000000000000000112731354143602500155020ustar00rootroot00000000000000# case-y.tst: yash-specific test of case command ( posix="true" test_Oe -e 2 'reserved word esac as pattern (-o POSIX)' case x in (esac) echo not reached; esac __IN__ syntax error: an unquoted `esac' cannot be the first case pattern __ERR__ #' #` ) test_oe 'patterns separated by | are expanded and matched in order' case 1 in $(echo expanded 0 >&2; echo 0) |\ $(echo expanded 1 >&2; echo 1) |\ $(echo expanded 2 >&2; echo 2)) echo matched;; esac __IN__ matched __OUT__ expanded 0 expanded 1 __ERR__ # The behavior is unspecified in POSIX, but many existing shells seem to behave # this way (with the notable exception of ksh). test_OE -e 0 'exit status of case command (matched, empty)' case $(echo 2; exit 2) in 1) ;; 2) ;; 3) ;; esac __IN__ test_oE 'reserved word esac as pattern (preceded by parenthesis, +o POSIX)' case esac in (esac) echo matched;; esac __IN__ matched __OUT__ test_Oe -e 2 'in without case' in __IN__ syntax error: `in' cannot be used as a command name __ERR__ #' #` test_Oe -e 2 ';; outside case (at beginning of line)' ;; __IN__ syntax error: `;;' is used outside `case' __ERR__ #' #` #' #` test_Oe -e 2 ';; outside case (after simple command)' echo foo;; __IN__ syntax error: `;' or `&' is missing __ERR__ #' #` #' #` test_Oe -e 2 'esac without case' esac __IN__ syntax error: encountered `esac' without a matching `case' __ERR__ #' #` #' #` test_Oe -e 2 'case followed by EOF' case __IN__ syntax error: a word is required after `case' syntax error: `in' is missing syntax error: `esac' is missing __ERR__ #' #` #' #` #' #` test_Oe -e 2 'case followed by symbol' case file test_oE 'default operand is HOME (-L)' HOME=/dev cd -L echo --- $? pwd __IN__ --- 0 /dev __OUT__ test_oE 'default operand is HOME (-P)' HOME=/dev cd -P echo --- $? pwd __IN__ --- 0 /dev __OUT__ ( # Ensure $PWD is safe to assign to $PATH case $PWD in (*[:%]*) skip="true" esac testcase "$LINENO" 'found in first cd path (-L)' \ 3<<\__IN__ 5&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/checkfg.c000066400000000000000000000022241354143602500153170ustar00rootroot00000000000000/* checkfg.c: checks if the current process is in the foreground */ /* (C) 2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _POSIX_C_SOURCE 200112L #include #include #include int main(void) { int ttyfd = open("/dev/tty", O_RDWR | O_NOCTTY | O_NONBLOCK); if (ttyfd < 0) return EXIT_FAILURE; pid_t tpgid = tcgetpgrp(ttyfd); if (tpgid < 0) return EXIT_FAILURE; pid_t pgid = getpgrp(); return tpgid == pgid ? EXIT_SUCCESS : EXIT_FAILURE; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/tests/cmdprint-y.tst000066400000000000000000000316221354143602500164070ustar00rootroot00000000000000# cmdprint-y.tst: yash-specific test of command printing mkfifo fifo testcase_single() { testcase "$@" 3<<__IN__ 4<<__OUT__ $(cat <&3) & jobs >fifo __IN__ [1] + Running $(cat <&4) __OUT__ } testcase_multi() { testcase "$@" 3<<__IN__ 4<<__OUT__ eval "\$( f() $(cat <&3) typeset -fp f )" typeset -fp f __IN__ f() $(cat <&4) __OUT__ } alias test_single='testcase_single "$LINENO" 3<<\__IN__ 4<<\__OUT__' alias test_multi='testcase_multi "$LINENO" 3<<\__IN__ 4<<\__OUT__' test_single 'one simple command, single line' cat fifo __IN__ cat fifo __OUT__ test_multi 'one simple command, multi-line' { echo; } typeset -fp f __IN__ { echo } __OUT__ test_single 'many and-or lists, ending synchronously, single line' { cat fifo; exit ; foo & bar& ls ls -l; } __IN__ { cat fifo; exit; foo& bar& ls; ls -l; } __OUT__ test_single 'many and-or lists, ending asynchronously, single line' { :& true & cat fifo; exit foo & bar& } __IN__ { :& true& cat fifo; exit; foo& bar& } __OUT__ test_multi 'many and-or lists, multi-line' { echo ; echo 1 ; foo & bar & ls ; ls -l; } typeset -fp f __IN__ { echo echo 1 foo& bar& ls ls -l } __OUT__ test_single 'many pipelines, single line' cat fifo && : || echo not reached __IN__ cat fifo && : || echo not reached __OUT__ test_multi 'many pipelines, multi-line' { cat fifo || echo not reached && :; } __IN__ { cat fifo || echo not reached && : } __OUT__ test_single 'many commands, single line' cat fifo | cat - | cat __IN__ cat fifo | cat - | cat __OUT__ test_multi 'many commands, multi-line' { echo | cat - | cat; } __IN__ { echo | cat - | cat } __OUT__ test_single 'negated pipeline, single line' ! cat fifo | cat - | cat __IN__ ! cat fifo | cat - | cat __OUT__ test_multi 'negated pipeline, multi-line' { ! echo | cat - | cat; } __IN__ { ! echo | cat - | cat } __OUT__ # Non-empty grouping is tested in other tests above. test_single 'grouping, w/o commands, single line' { } && cat fifo __IN__ { } && cat fifo __OUT__ test_multi 'grouping, w/o commands, multi-line' { } __IN__ { } __OUT__ test_single 'subshell, w/ single command, ending synchronously, single line' (cat fifo) __IN__ (cat fifo) __OUT__ test_single 'subshell, w/ many commands, ending asynchronously, single line' (cat fifo; :&) __IN__ (cat fifo; :&) __OUT__ test_multi 'subshell, w/ simple command, ending synchronously, multi-line' (cat fifo) __IN__ (cat fifo) __OUT__ test_multi 'subshell, w/ many commands, ending asynchronously, multi-line' (:; cat fifo; :&) __IN__ (: cat fifo :&) __OUT__ test_single 'subshell, w/o commands, single line' () && cat fifo __IN__ () && cat fifo __OUT__ test_single 'if command, w/o elif, w/o else, single line' if :& :; then cat fifo; fi __IN__ if :& :; then cat fifo; fi __OUT__ test_multi 'if command, w/o elif, w/o else, multi-line' if :& : then cat fifo fi __IN__ if :& : then cat fifo fi __OUT__ test_single 'if command, w/ elif, w/o else, single line' if :& :; then cat fifo; :& elif foo; then :; elif bar& then :& fi __IN__ if :& :; then cat fifo; :& elif foo; then :; elif bar& then :& fi __OUT__ test_multi 'if command, w/ elif, w/o else, multi-line' if [ ]; then foo& elif 1; then 2; elif a& b; then c& fi __IN__ if [ ] then foo& elif 1 then 2 elif a& b then c& fi __OUT__ test_single 'if command, w/o elif, w/ else, single line' if :& :; then cat fifo; else echo not reached; fi __IN__ if :& :; then cat fifo; else echo not reached; fi __OUT__ test_multi 'if command, w/o elif, w/ else, multi-line' if :& :; then cat fifo else echo not reached fi __IN__ if :& : then cat fifo else echo not reached fi __OUT__ test_single 'if command, w/ elif, w/ else, single line' if :; then cat fifo; elif foo& then :; else bar& fi __IN__ if :; then cat fifo; elif foo& then :; else bar& fi __OUT__ test_multi 'if command, w/ elif, w/ else, multi-line' if :; then cat fifo; elif foo& then :; else bar& fi __IN__ if : then cat fifo elif foo& then : else bar& fi __OUT__ test_single 'if command, w/o commands, single line' if then elif then elif then else fi && cat fifo __IN__ if then elif then elif then else fi && cat fifo __OUT__ test_multi 'if command, w/o commands, multi-line' if then elif then elif then else fi __IN__ if then elif then elif then else fi __OUT__ test_single 'for command, w/o in, single line' set 1 && for i do cat fifo; done __IN__ set 1 && for i do cat fifo; done __OUT__ test_multi 'for command, w/o in, multi-line' for i do cat fifo; done __IN__ for i do cat fifo done __OUT__ test_single 'for command, w/ in, w/o words, single line' { for i in; do echo not reached; done; cat fifo; } __IN__ { for i in; do echo not reached; done; cat fifo; } __OUT__ test_multi 'for command, w/ in, w/o words, multi-line' for i in; do echo not reached; done __IN__ for i in do echo not reached done __OUT__ test_single 'for command, w/ in, w/ many words, single line' for i in 1 2 3; do cat fifo; break; :& done __IN__ for i in 1 2 3; do cat fifo; break; :& done __OUT__ test_multi 'for command, w/ in, w/ many words, multi-line' for i in 1 2 3; do cat fifo; :; :& done __IN__ for i in 1 2 3 do cat fifo : :& done __OUT__ test_single 'for command, w/ commands, single line' for i do done && cat fifo __IN__ for i do done && cat fifo __OUT__ test_multi 'for command, w/ commands, multi-line' for i do done __IN__ for i do done __OUT__ test_single 'while command, w/ single command condition, single line' while :; do cat fifo; break; done __IN__ while :; do cat fifo; break; done __OUT__ test_multi 'while command, w/ single command condition, multi-line' while :; do foo; done __IN__ while : do foo done __OUT__ test_single 'while command, w/ many command condition, single line' while cat fifo; break; :& do foo; bar& done __IN__ while cat fifo; break; :& do foo; bar& done __OUT__ test_multi 'while command, w/ many command condition, multi-line' while cat fifo; break; :& do foo; bar& done __IN__ while cat fifo break :& do foo bar& done __OUT__ test_single 'while command, w/o commands, single line' cat fifo || exit || while do done __IN__ cat fifo || exit || while do done __OUT__ test_multi 'while command, w/o commands, multi-line' while do done __IN__ while do done __OUT__ test_single 'case command, w/o case items, single line' case i in esac && cat fifo __IN__ case i in esac && cat fifo __OUT__ test_multi 'case command, w/o case items, multi-line' case i in esac __IN__ case i in esac __OUT__ test_single 'case command, w/ case items, single line' case i in (i) cat fifo;; (j) foo& bar;; (k|l|m) ;; (n) :& esac __IN__ case i in (i) cat fifo ;; (j) foo& bar ;; (k | l | m) ;; (n) :& ;; esac __OUT__ test_multi 'case command, w/ case items, multi-line' case i in (i) cat fifo;; (j) foo& bar;; (k|l|m) ;; (n) :& esac __IN__ case i in (i) cat fifo ;; (j) foo& bar ;; (k | l | m) ;; (n) :& ;; esac __OUT__ ( if ! testee -c 'command -v [[' >/dev/null; then skip="true" fi test_multi 'double bracket, string primary' [[ "foo" ]] __IN__ [[ "foo" ]] __OUT__ test_multi 'double bracket, unary/binary primaries' [[ -n foo || 0 -eq '1' || a = a || x < y ]] __IN__ [[ -n foo || 0 -eq '1' || a = a || x < y ]] __OUT__ test_multi 'double bracket, disjunction in disjunction' [[ (a || b) || (c || d) ]] __IN__ [[ a || b || c || d ]] __OUT__ test_multi 'double bracket, conjunction in disjunction' [[ (a && b) || (c && d) ]] __IN__ [[ a && b || c && d ]] __OUT__ test_multi 'double bracket, negation in disjunction' [[ (! a) || (! b) ]] __IN__ [[ ! a || ! b ]] __OUT__ test_multi 'double bracket, disjunction in conjunction' [[ (a || b) && (c || d) ]] __IN__ [[ ( a || b ) && ( c || d ) ]] __OUT__ test_multi 'double bracket, conjunction in conjunction' [[ (a && b) && (c && d) ]] __IN__ [[ a && b && c && d ]] __OUT__ test_multi 'double bracket, negation in conjunction' [[ (! a) && (! b) ]] __IN__ [[ ! a && ! b ]] __OUT__ test_multi 'double bracket, disjunction in negation' [[ ! (a || b) ]] __IN__ [[ ! ( a || b ) ]] __OUT__ test_multi 'double bracket, conjunction in negation' [[ ! (a && b) ]] __IN__ [[ ! ( a && b ) ]] __OUT__ test_multi 'double bracket, negation in negation' [[ ! (! a) ]] __IN__ [[ ! ! a ]] __OUT__ ) test_single 'function definition, POSIX name, single line' f() { :; } >/dev/null && cat fifo __IN__ f() { :; } 1>/dev/null && cat fifo __OUT__ # POSIX-name multi-line function definition is tested in all testcase_multi # test cases. test_single 'function definition, non-POSIX name, single line' function "${a-f}" { :; } && cat fifo __IN__ function "${a-f}"() { :; } && cat fifo __OUT__ test_multi 'function definition, non-POSIX name, multi-line' { function "${a-f}" { :; } } __IN__ { function "${a-f}"() { : } } __OUT__ test_multi 'scalar assignment' { foo= bar=BAR; } __IN__ { foo= bar=BAR } __OUT__ test_multi 'array assignment' { foo=() bar=(1 $2 3); } __IN__ { foo=() bar=(1 ${2} 3) } __OUT__ test_multi 'single-line redirections' { g 2>|h 10>>i <>j <&1 >&2 >>|"3" <<g 2>|h 10>>i 0<>j 0<&1 1>&2 1>>|"3" 0<</dev/null do do; } __IN__ { foo=bar if then fi \do do 1>/dev/null } __OUT__ test_single 'here-documents, single line' { cat fifo <( :& echo bar ); } __IN__ { 0<(:& echo foo) 1>(:& echo bar) } __OUT__ test_multi 'complex simple command' { >/dev/null v=0 3/dev/null : <(); } __IN__ { v=0 echo : 1>/dev/null 3/dev/null 0<() } __OUT__ test_multi 'word w/ expansions' { echo ~/"$1"/$2/${foo}/$((1 + $3))/$(echo 5)/`echo 6`; } __IN__ { echo ~/"${1}"/${2}/${foo}/$((1 + ${3}))/$(echo 5)/$(echo 6) } __OUT__ test_multi 'parameter expansion, #-prefixed' { echo "${#3}"; } __IN__ { echo "${#3}" } __OUT__ test_multi 'parameter expansion, nested' { echo "${{#3}-unset}"; } __IN__ { echo "${${#3}-unset}" } __OUT__ test_multi 'parameter expansion, indexed' { echo "${foo[1]}${bar[2,$((1+2))]}"; } __IN__ { echo "${foo[1]}${bar[2,$((1+2))]}" } __OUT__ test_multi 'parameter expansion, w/ basic modifier' { echo "${foo:+1}${bar-2}${baz:=3}${xxx?4}"; } __IN__ { echo "${foo:+1}${bar-2}${baz:=3}${xxx?4}" } __OUT__ test_multi 'parameter expansion, w/ matching' { echo "${foo#x}${bar##y}${baz%z}${xxx%%0}"; } __IN__ { echo "${foo#x}${bar##y}${baz%z}${xxx%%0}" } __OUT__ test_multi 'parameter expansion, w/ substitution' { echo "${a/x}${b//y}${c/#z/Z}${d/%0/0}"; } __IN__ { echo "${a/x/}${b//y/}${c/#z/Z}${d/%0/0}" } __OUT__ test_multi 'command substitution starting with subshell' { echo "$((foo);)"; } __IN__ { echo "$( (foo))" } __OUT__ test_multi 'backquoted command substitution' { echo "`echo \`echo foo\``"; } __IN__ { echo "$(echo $(echo foo))" } __OUT__ test_multi 'complex indentation of compound commands' { ( if :; foo; then for i in 1 2 3; do while :; foo& do case i in (i) [[ foo ]] f() { cat - /dev/null <<-END $(here; document) END echo ${foo-$((1 + $(bar; baz)))} cat <(foo; bar <<-END END ) >/dev/null } esac done until :; bar& do : done done elif :; bar& then : else baz fi ) } __IN__ { (if : foo then for i in 1 2 3 do while : foo& do case i in (i) [[ foo ]] f() { cat - /dev/null 0<<-END $(here document) END echo ${foo-$((1 + $(bar baz)))} cat 0<(foo bar 0<<-END END ) 1>/dev/null } ;; esac done until : bar& do : done done elif : bar& then : else baz fi) } __OUT__ test_single 'nested here-documents and command substitutions, single line' { cat fifo < dummyfile setup -d test_oE -e 0 'result of command substitution' a=$(echo a) && bracket $a`echo b` __IN__ [ab] __OUT__ test_oE 'command substitution executes in subshell' a=a b=$(a=x; echo b) bracket $a$b __IN__ [ab] __OUT__ test_oE 'trailing newlines are removed' a=$(printf 'x\ny') b=$(printf 'x\ny\n') c=$(printf 'x\n\ny\n\n\n\n') bracket "$a" "$b" "$c" __IN__ [x y][x y][x y] __OUT__ test_oE 'stdin is not redirected' echo a | echo $(cat) __IN__ a __OUT__ test_oe -e 0 'stderr is not redirected' bracket "$(echo x >&2)" __IN__ [] __OUT__ x __ERR__ test_oE 'field splitting on result of command substitution' bracket $(printf 'a\n\nb') __IN__ [a][b] __OUT__ test_oE 'backslash in backquotes / nested backquotes' echoraw `echoraw \`echoraw x\`` echoraw `echoraw '\$y'` echoraw `printf '%s\n' \\\\` __IN__ x $y \ __OUT__ test_oE 'quotations in backquotes' echoraw `echoraw "a"'b'` echoraw `echoraw \$ "\$" '\$'` echoraw `echoraw \\\\ "\\\\" '\\\\'` echoraw `echoraw \" "\"" '\"'` echoraw `echoraw \' "\'"` echoraw `echoraw \`echo a\` "\`echo b\`" '\`echo c\`'` __IN__ ab $ $ $ \ \ \\ " " \" ' \' a b `echo c` __OUT__ test_oE 'quotations in backquotes in double quotes' echoraw "`echoraw "a"'b'`" echoraw "`echoraw \$ "\$" '\$'`" echoraw "`echoraw \\\\ "\\\\" '\\\\'`" echoraw "`echoraw \"1\"`" echoraw "`echoraw \'2\'`" echoraw "`echoraw \`echo a\` "\`echo b\`" '\`echo c\`'`" __IN__ ab $ $ $ \ \ \\ 1 '2' a b `echo c` __OUT__ test_oE 'quotations in backquotes in here-document' cat <foo chmod a+x foo test_x -e 0 'exit status of describing external command (-v, with slash)' command -v ./foo __IN__ test_x -e 0 'exit status of describing external command (-V, with slash)' command -V ./foo __IN__ test_E -e 0 'output of describing external command (-v, with slash)' command -v ./foo | grep '^/' | grep '/foo$' __IN__ test_E -e 0 'output of describing external command (-V, with slash)' command -V ./foo | grep -F "$(command -v ./foo)" __IN__ test_oE -e 0 'describing function (-v)' cat() { :; } command -v cat __IN__ cat __OUT__ test_E -e 0 'describing function (-V)' cat() { :; } command -V cat __IN__ test_oE -e 0 'describing alias (-v)' alias abc='echo ABC' command="$(command -v abc)" unalias abc eval "$command" abc __IN__ ABC __OUT__ test_OE -e 0 'describing alias (-V)' alias abc=xyz d="$(command -V abc)" case "$d" in (*abc*xyz*|*xyz*abc*) # expected output contains alias name and value ;; (*) printf '%s\n' "$d" # print non-conforming result ;; esac __IN__ test_OE -e n 'describing non-existent command (-v)' PATH= command -v _no_such_command_ __IN__ test_x -e n 'describing non-existent command (-V)' PATH= command -V _no_such_command_ __IN__ test_x -e 0 'describing external command with standard path (-v)' PATH= command -pv cat __IN__ test_x -e 0 'describing external command with standard path (-V)' PATH= command -pV cat __IN__ ) test_O -d -e 127 'executing non-existent command' command ./_no_such_command_ __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/command-y.tst000066400000000000000000000144471354143602500162130ustar00rootroot00000000000000# command-y.tst: yash-specific test of the command and type built-ins # Seemingly meaningless comments like #` in this script are to work around # syntax highlighting errors on some editors. test_oE -e 0 'executing with -b option' command -b eval echo foo __IN__ foo __OUT__ test_Oe -e 127 'external command is not found with -b option' command -b cat /dev/null __IN__ command: no such command `cat' __ERR__ #` test_OE -e 0 'executing with -e option' command -e cat /dev/null __IN__ test_Oe -e 127 'built-in command is not found with -e option' PATH= command -e exit 10 __IN__ command: no such command `exit' __ERR__ #` test_oE -e 0 'executing with -f option' exit() { echo foo; } command -f exit 1 __IN__ foo __OUT__ test_oE -e 0 'executing function with name containing slash' function foo/bar { echo "$@" } command -f foo/bar baz 'x x' __IN__ baz x x __OUT__ test_Oe -e 127 'external command is not found with -f option' command -f cat /dev/null __IN__ command: no such command `cat' __ERR__ #` test_oE -e 0 'describing alias (-V)' alias a='foo' command -V a __IN__ a: an alias for `foo' __OUT__ #` test_oE -e 0 'describing special built-ins (-V)' command -V : . break continue eval exec exit export readonly return set shift \ times trap unset __IN__ :: a special built-in .: a special built-in break: a special built-in continue: a special built-in eval: a special built-in exec: a special built-in exit: a special built-in export: a special built-in readonly: a special built-in return: a special built-in set: a special built-in shift: a special built-in times: a special built-in trap: a special built-in unset: a special built-in __OUT__ # `newgrp' is not a semi-special built-in in yash. test_oE -e 0 'describing semi-special built-ins (-V)' command -V bg cd command false fg getopts hash jobs kill pwd read true type \ umask wait __IN__ bg: a semi-special built-in cd: a semi-special built-in command: a semi-special built-in false: a semi-special built-in fg: a semi-special built-in getopts: a semi-special built-in hash: a semi-special built-in jobs: a semi-special built-in kill: a semi-special built-in pwd: a semi-special built-in read: a semi-special built-in true: a semi-special built-in type: a semi-special built-in umask: a semi-special built-in wait: a semi-special built-in __OUT__ ( if ! testee -c 'command -bv ulimit' >/dev/null; then skip="true" fi test_oE -e 0 'describing semi-special built-in ulimit (-V)' command -V ulimit __IN__ ulimit: a semi-special built-in __OUT__ ) ( if ! testee -c 'command -bv echo' >/dev/null; then skip="true" fi test_OE 'describing regular built-in (-V)' command -V echo | grep -v "^echo: a regular built-in " __IN__ ) test_OE -e 0 'describing external command (-V)' command -V cat | grep -q '^cat: an external command at' __IN__ test_oE -e 0 'describing function (-V)' true() { :; } type -V true __IN__ true: a function __OUT__ test_oE -e 0 'describing reserved words (-V)' command -V if then else elif fi do done case esac while until for function \ { } ! in __IN__ if: a shell keyword then: a shell keyword else: a shell keyword elif: a shell keyword fi: a shell keyword do: a shell keyword done: a shell keyword case: a shell keyword esac: a shell keyword while: a shell keyword until: a shell keyword for: a shell keyword function: a shell keyword {: a shell keyword }: a shell keyword !: a shell keyword in: a shell keyword __OUT__ test_oE -e 0 'describing alias with -a option' alias a='foo' command -va a && command --identify --alias a __IN__ alias a=foo alias a=foo __OUT__ test_oE -e 0 'describing built-ins with -b option' command -vb : bg && command --identify --builtin-command : bg __IN__ : bg : bg __OUT__ test_E -e 0 'describing external command with -e option' command -ve cat && command --identify --external-command cat __IN__ test_oE -e 0 'describing function with -f option' true() { :; } command -vf true && command --identify --function true __IN__ true true __OUT__ test_oE -e 0 'describing reserved word with -k option' command -vk if && command --identify --keyword if __IN__ if if __OUT__ test_OE -e 1 'describing non-existent command (-va)' command -va exit __IN__ test_OE -e 1 'describing non-existent command (-vb)' command -vb cat __IN__ test_OE -e 1 'describing non-existent command (-ve)' PATH= command -ve exit __IN__ test_OE -e 1 'describing non-existent command (-vk)' command -vk exit __IN__ test_OE -e 1 'describing non-existent command (-vf)' command -vf exit __IN__ test_Oe -e 1 'describing non-existent command (-V)' PATH= command -V _no_such_command_ __IN__ command: no such command `_no_such_command_' __ERR__ #` test_oE -e 0 'describing with long option' command --verbose-identify if : bg __IN__ if: a shell keyword :: a special built-in bg: a semi-special built-in __OUT__ test_oE -e 0 'describing with type command' type if : bg __IN__ if: a shell keyword :: a special built-in bg: a semi-special built-in __OUT__ test_O -d -e 1 'printing to closed stream' command -v command >&- __IN__ test_Oe -e n 'using -a without -v' command -a : __IN__ command: the -a or -k option must be used with the -v option __ERR__ test_Oe -e n 'using -k without -v' command -k : __IN__ command: the -a or -k option must be used with the -v option __ERR__ test_Oe -e n 'invalid option' command --no-such-option __IN__ command: `--no-such-option' is not a valid option __ERR__ #` test_OE -e 0 'missing operand (non-POSIX)' command __IN__ ( posix="true" test_oe 'argument syntax error in special built-in does not kill shell' command . # missing operand echo reached __IN__ reached __OUT__ .: this command requires an operand __ERR__ test_Oe -e n 'missing operand (w/o -v, POSIX)' command __IN__ command: this command requires an operand __ERR__ test_Oe -e n 'missing operand (with -v, POSIX)' command -v __IN__ command: this command requires an operand __ERR__ test_Oe -e n 'missing operand (with -V, POSIX)' command -V __IN__ command: this command requires an operand __ERR__ test_Oe -e n 'missing operand (type, POSIX)' type __IN__ type: this command requires an operand __ERR__ test_Oe -e n 'more than one operand (with -v, POSIX)' command -v foo bar __IN__ command: too many operands are specified __ERR__ test_Oe -e n 'more than one operand (with -V, POSIX)' command -V foo bar __IN__ command: too many operands are specified __ERR__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/comment-p.tst000066400000000000000000000035431354143602500162210ustar00rootroot00000000000000# comment-p.tst: test of comments for any POSIX-compliant shell test_OE 'comment without command' # # foo # bar # # ##foo ### __IN__ test_oE 'comment ending with backslash' # \ echo foo __IN__ foo __OUT__ test_oE 'comment in simple command' v=abc sh -c 'echo $v "$@"' # 0 1 2 3 echo 123 # 456 # 789; echo xyz /dev/null; then skip="true" fi test_oE -e 0 'complete is a semi-special built-in' command -V complete __IN__ complete: a semi-special built-in __OUT__ test_Oe -e 2 'invalid option' complete --no-such-option __IN__ complete: `--no-such-option' is not a valid option __ERR__ #` test_Oe -e 2 'not during completion' complete __IN__ complete: the complete built-in can be used during command line completion only __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/continue-p.tst000066400000000000000000000160461354143602500164050ustar00rootroot00000000000000# continue-p.tst: test of the continue built-in for any POSIX-compliant shell posix="true" test_oE 'continuing one for loop, unnested' for i in 1 2 3; do echo in $i continue 1 echo out $i done echo done $? __IN__ in 1 in 2 in 3 done 0 __OUT__ test_oE 'continuing one while loop, unnested' i=1 while [ $i -le 3 ]; do echo in $i i=$((i+1)) continue 1 echo out $i done echo done $? __IN__ in 1 in 2 in 3 done 0 __OUT__ test_oE 'continuing one until loop, unnested' i=1 until [ $i -gt 3 ]; do echo in $i i=$((i+1)) continue 1 echo out $i done echo done $? __IN__ in 1 in 2 in 3 done 0 __OUT__ test_oE 'continuing one for loop, nested in for loop' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j continue 1 echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 b in 1 c out 1 in 2 in 2 a in 2 b in 2 c out 2 in 3 in 3 a in 3 b in 3 c out 3 done 0 __OUT__ test_oE 'continuing one for loop, nested in while loop' i=1 while [ $i -le 3 ]; do echo in $i for j in a b c; do echo in $i $j continue 1 echo out $i $j done echo out $i i=$((i+1)) done echo done $? __IN__ in 1 in 1 a in 1 b in 1 c out 1 in 2 in 2 a in 2 b in 2 c out 2 in 3 in 3 a in 3 b in 3 c out 3 done 0 __OUT__ test_oE 'continuing one while loop, nested in while loop' i=1 while [ $i -le 3 ]; do echo in outer $i j=1 while [ $j -le 3 ]; do echo in inner $i $j j=$((j+1)) continue 1 echo out inner $i $j done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 1 in inner 1 2 in inner 1 3 out outer 1 in outer 2 in inner 2 1 in inner 2 2 in inner 2 3 out outer 2 in outer 3 in inner 3 1 in inner 3 2 in inner 3 3 out outer 3 done 0 __OUT__ test_oE 'continuing one while loop, nested in until loop' i=1 until [ $i -gt 3 ]; do echo in outer $i j=1 while [ $j -le 3 ]; do echo in inner $i $j j=$((j+1)) continue 1 echo out inner $i $j done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 1 in inner 1 2 in inner 1 3 out outer 1 in outer 2 in inner 2 1 in inner 2 2 in inner 2 3 out outer 2 in outer 3 in inner 3 1 in inner 3 2 in inner 3 3 out outer 3 done 0 __OUT__ test_oE 'continuing one until loop, nested in until loop' i=1 until [ $i -gt 3 ]; do echo in outer $i j=1 until [ $j -gt 3 ]; do echo in inner $i $j j=$((j+1)) continue 1 echo out inner $i $j done echo out outer $i i=$((i+1)) done echo done $? __IN__ in outer 1 in inner 1 1 in inner 1 2 in inner 1 3 out outer 1 in outer 2 in inner 2 1 in inner 2 2 in inner 2 3 out outer 2 in outer 3 in inner 3 1 in inner 3 2 in inner 3 3 out outer 3 done 0 __OUT__ test_oE 'continuing two for loops, outermost' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j continue 2 echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 2 in 2 a in 3 in 3 a done 0 __OUT__ test_oE 'continuing for and while loops, outermost' i=1 while [ $i -le 3 ]; do echo in $i for j in a b c; do echo in $i $j i=$((i+1)) continue 2 echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 2 in 2 a in 3 in 3 a done 0 __OUT__ test_oE 'continuing two while loops, outermost' i=1 while [ $i -le 3 ]; do echo in outer $i while true; do echo in inner $i i=$((i+1)) continue 2 echo out inner $i done echo out outer $i done echo done $? __IN__ in outer 1 in inner 1 in outer 2 in inner 2 in outer 3 in inner 3 done 0 __OUT__ test_oE 'continuing while and until loops, outermost' i=1 until [ $i -gt 3 ]; do echo in outer $i while true; do echo in inner $i i=$((i+1)) continue 2 echo out inner $i done echo out outer $i done echo done $? __IN__ in outer 1 in inner 1 in outer 2 in inner 2 in outer 3 in inner 3 done 0 __OUT__ test_oE 'continuing two for loops, nested in another for loop' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j for k in + -; do echo in $i $j $k continue 2 echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + in 1 b in 1 b + in 1 c in 1 c + out 1 in 2 in 2 a in 2 a + in 2 b in 2 b + in 2 c in 2 c + out 2 in 3 in 3 a in 3 a + in 3 b in 3 b + in 3 c in 3 c + out 3 done 0 __OUT__ test_oE 'continuing three for loops, outermost' for i in 1 2 3; do echo in $i for j in a b c; do echo in $i $j for k in + -; do echo in $i $j $k continue 3 echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + in 2 in 2 a in 2 a + in 3 in 3 a in 3 a + done 0 __OUT__ test_oE 'default operand is 1' for i in 1; do echo in $i for j in a; do echo in $i $j for k in + -; do echo in $i $j $k continue echo out $i $j $k done echo out $i $j done echo out $i done echo done $? __IN__ in 1 in 1 a in 1 a + in 1 a - out 1 a out 1 done 0 __OUT__ test_OE -e 0 'exit status of continue with $? > 0' for i in 1; do false continue done __IN__ test_O -d -e n 'zero operand' for i in 1; do continue 0 done __IN__ test_OE 'continuing one more than actual nest level one' for i in 1; do continue 2 echo not reached done __IN__ test_OE 'continuing one more than actual nest level two' for i in 1; do for j in a; do continue 3 echo not reached 1 done echo not reached 2 done __IN__ test_OE 'continuing much more than actual nest level one' for i in 1; do continue 100 echo not reached done __IN__ # This is a questionable case. Is this really a "lexically enclosing" loop as # defined in POSIX? Most shells (other than mksh) support this case. test_oE 'continuing out of eval' for i in 1 2; do echo $i eval continue echo not reached done __IN__ 1 2 __OUT__ test_OE 'continuing with !' for i in 1; do ! continue echo not reached done __IN__ test_OE 'continuing before &&' for i in 1; do continue && echo not reached 1 echo not reached 2 $? done __IN__ test_OE 'continuing after &&' for i in 1; do true && continue echo not reached $? done __IN__ test_OE 'continuing before ||' for i in 1; do continue || echo not reached 1 echo not reached 2 $? done __IN__ test_OE 'continuing after ||' for i in 1; do false || continue echo not reached $? done __IN__ test_OE 'continuing out of brace' for i in 1; do { continue; } echo not reached done __IN__ test_OE 'continuing out of if' for i in 1; do if continue; then echo not reached then; else echo not reached else; fi echo not reached done __IN__ test_OE 'continuing out of then' for i in 1; do if true; then continue; echo not reached then; else not reached else; fi echo not reached done __IN__ test_OE 'continuing out of else' for i in 1; do if false; then echo not reached then; else continue; not reached else; fi echo not reached done __IN__ test_OE 'continuing out of case' for i in 1; do case x in x) continue echo not reached in case esac echo not reached after esac done __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/continue-y.tst000066400000000000000000000051521354143602500164120ustar00rootroot00000000000000# continue-y.tst: yash-specific test of the continue built-in echo 'continue; echo \$?=$?' >continue test_oe 'continuing out of dot' for i in 1 2; do echo $i . ./continue done __IN__ 1 $?=2 2 $?=2 __OUT__ continue: not in a loop continue: not in a loop __ERR__ test_oe 'continuing out of function' c() { continue; echo \$?=$?; } for i in 1 2; do echo $i c done __IN__ 1 $?=2 2 $?=2 __OUT__ continue: not in a loop continue: not in a loop __ERR__ test_oe 'continuing out of subshell' for i in 1; do (continue) || echo ok done __IN__ ok __OUT__ continue: not in a loop __ERR__ test_oe 'continuing out of trap' trap 'continue || echo trapped' USR1 for i in 1; do kill -USR1 $$ echo ok done __IN__ trapped ok __OUT__ continue: not in a loop __ERR__ test_oE 'continuing iteration, unnested, short option' eval -i 'echo 1' 'continue -i; echo not reached' 'echo continued' __IN__ 1 continued __OUT__ test_oE 'continuing iteration, unnested, long option' eval -i 'echo 1' 'continue --iteration; echo not reached' 'echo continued' __IN__ 1 continued __OUT__ test_OE -e 17 'exit status of continued iteration' eval -i '(exit 17); continue -i' __IN__ test_oE 'continuing nested iteration' eval -i 'eval -i "continue -i; echo not reached" "echo 1"; echo 2' __IN__ 1 2 __OUT__ test_OE 'continuing loop out of iteration' for i in 1; do eval -i continue 'echo not reached 1' echo not reached 2 done __IN__ test_o 'continuing loop out of auxiliary not allowed' COMMAND_NOT_FOUND_HANDLER=(continue 'echo reached 1 $?') for i in 1; do ./_no_such_command_ echo reached 2 $? done __IN__ reached 1 0 reached 2 127 __OUT__ test_oE 'continuing iteration out of eval' eval -i 'eval "continue -i"; echo not reached' 'echo continued' __IN__ continued __OUT__ echo 'continue -i' >continue-i test_oE 'continuing iteration out of dot' eval -i '. ./continue-i; echo not reached' 'echo continued' __IN__ continued __OUT__ test_oE 'continuing iteration out of loop' eval -i 'for i in 1; do continue -i; done; echo not reached' 'echo continued' __IN__ continued __OUT__ test_Oe -e n 'continuing without target loop' continue __IN__ continue: not in a loop __ERR__ test_Oe -e n 'continuing without target iteration' continue -i __IN__ continue: not in an iteration __ERR__ test_Oe -e n 'too many operands' continue 1 2 __IN__ continue: too many operands are specified __ERR__ test_Oe -e n 'operand and -i' continue -i 1 __IN__ continue: no operand is expected __ERR__ test_Oe -e n 'invalid option' continue --no-such-option __IN__ continue: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/dirstack-y.tst000066400000000000000000000361021354143602500163710ustar00rootroot00000000000000# dirstack-y.tst: yash-specific test of directory stack if ! testee -c 'command -bv pushd' >/dev/null; then skip="true" fi cd -P . # make $PWD a physical path mkdir testdir testdir/1 testdir/2 testdir/3 mkdir -m 000 testdir/000 ln -s .. testdir/parent ##### dirs test_oE -e 0 'dirs is a semi-special built-in' command -V dirs __IN__ dirs: a semi-special built-in __OUT__ testcase "$LINENO" -e 0 'printing unset directory stack' \ 3<<\__IN__ 4<<__OUT__ 5&- __IN__ test_oE -e 0 'clearing directory stack (-c)' DIRSTACK=(a b c) dirs -c && echo "${DIRSTACK-unset}" __IN__ unset __OUT__ test_oE -e 0 'clearing directory stack (--clear)' DIRSTACK=(a b c) dirs --clear && echo "${DIRSTACK-unset}" __IN__ unset __OUT__ test_Oe -e n 'clearing read-only directory stack' DIRSTACK=(a) readonly DIRSTACK dirs -c __IN__ dirs: $DIRSTACK is read-only __ERR__ test_Oe -e n 'dirs: invalid option' dirs --no-such-option __IN__ dirs: `--no-such-option' is not a valid option __ERR__ #` ##### pushd test_oE -e 0 'pushd is a semi-special built-in' command -V pushd __IN__ pushd: a semi-special built-in __OUT__ testcase "$LINENO" 'pushing directory' \ 3<<\__IN__ 4<<__OUT__ 5&- __IN__ test_Oe -e n 'pushd: invalid option' pushd --no-such-option __IN__ pushd: `--no-such-option' is not a valid option __ERR__ #` test_Oe -e n 'pushd: too many operands' pushd +0 +0 __IN__ pushd: too many operands are specified __ERR__ ##### popd test_oE -e 0 'popd is a semi-special built-in' command -V popd __IN__ popd: a semi-special built-in __OUT__ test_Oe -e n 'popping default directory from empty stack' popd __IN__ popd: the directory stack is empty __ERR__ testcase "$LINENO" 'popping default directory from 1-element stack' \ 3<<\__IN__ 4<<__OUT__ 5&- __IN__ test_Oe -e n 'popd: invalid option' popd --no-such-option __IN__ popd: `--no-such-option' is not a valid option __ERR__ #` test_Oe -e n 'popd: too many operands' popd +0 +0 __IN__ popd: too many operands are specified __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/disown-y.tst000066400000000000000000000006631354143602500160730ustar00rootroot00000000000000# disown-y.tst: yash-specific test of the disown built-in test_OE -e 0 'omitting % in job ID' sleep 1& disown sleep __IN__ test_oE -e 0 'disown is a semi-special built-in' command -V disown __IN__ disown: a semi-special built-in __OUT__ ( posix="true" test_Oe -e 1 'initial % cannot be omitted in POSIX mode' disown foo __IN__ disown: `foo' is not a valid job specification __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/dot-p.tst000066400000000000000000000034741354143602500153500ustar00rootroot00000000000000# dot-p.tst: test of the dot built-in for any POSIX-compliant shell posix="true" cat <<\__END__ >file1 echo $? (exit 3) __END__ cat <<\__END__ >file2 echo in . ./file1 echo out __END__ cat <<\__END__ >file3 exit 11 __END__ test_OE -e 0 'empty dot script' (exit 1) . /dev/null __IN__ test_oE -e 3 'non-empty dot script' (exit 5) . ./file1 __IN__ 5 __OUT__ test_oE -e 0 'recursive dot script' . ./file2 __IN__ in 0 out __OUT__ ( # Ensure $PWD is safe to assign to $PATH case $PWD in (*[:%]*) skip="true" esac setup 'savepath=$PATH; PATH=$PWD' test_OE -e 11 'dot script in $PATH' . file3 __IN__ test_O -d -e n 'dot script not found, in $PATH, non-interactive shell' . _no_such_file_ PATH=$savepath echo not reached __IN__ test_o -d 'dot script not found, in $PATH, subshell, exiting' (. _no_such_file_) PATH=$savepath echo reached __IN__ reached __OUT__ test_O -d -e n 'dot script not found, in $PATH, subshell, exit status' (. _no_such_file_) __IN__ test_o -d 'dot script not found, in $PATH, interactive shell, no exiting' -i +m . _no_such_file_ PATH=$savepath echo reached __IN__ reached __OUT__ test_O -d -e n 'dot script not found, in $PATH, interactive shell, exit status' -i +m . _no_such_file_ __IN__ ) test_O -d -e n 'dot script not found, relative, non-interactive shell' . ./_no_such_file_ echo not reached __IN__ test_o -d 'dot script not found, relative, subshell, exiting' (. ./_no_such_file_) echo reached __IN__ reached __OUT__ test_O -d -e n 'dot script not found, relative, subshell, exit status' (. ./_no_such_file_) __IN__ test_o -d 'dot script not found, relative, interactive shell, no exiting' -i +m . ./_no_such_file_ echo reached __IN__ reached __OUT__ test_O -d -e n 'dot script not found, relative, interactive shell, exit status' -i +m . ./_no_such_file_ __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/dot-y.tst000066400000000000000000000052601354143602500153540ustar00rootroot00000000000000# dot-y.tst: yash-specific test of the dot built-in echo true >true echo 'echo "$*"' >print_args echo 'set a "$@"; echo "$*"' >set_print_args test_oE 'positional parameters in dot script' set 1 2 3 . ./print_args __IN__ 1 2 3 __OUT__ test_oE 'changing outer-scope positional parameters in dot script' set 1 2 3 . ./set_print_args echo "$*" __IN__ a 1 2 3 a 1 2 3 __OUT__ test_oE 'temporary positional parameters' set 1 2 3 . ./print_args x y echo "$*" __IN__ x y 1 2 3 __OUT__ test_oE 'changing temporary positional parameters' set 1 2 3 . ./set_print_args x y echo "$*" __IN__ a x y 1 2 3 __OUT__ test_o 'positional parameters removed on error' set foo command . ./_no_such_file_ bar echo "$*" __IN__ foo __OUT__ ( setup 'alias true=false' test_OE -e n 'alias in dot script (POSIX)' --posix . ./true __IN__ test_OE -e n 'alias in dot script (non-POSIX)' . ./true __IN__ test_OE -e 0 'disabling alias (short option)' . -A ./true __IN__ test_OE -e 0 'disabling alias (long option)' . --no-alias ./true __IN__ ) ( # Ensure $PWD is safe to assign to $PATH/$YASH_LOADPATH case $PWD in (*[:%]*) skip="true" esac mkdir testpath echo 'echo foo' >testpath/foo mkdir testpath/dir echo 'echo bar' >testpath/dir/bar ( setup 'YASH_LOADPATH="$PWD/testpath"' test_oE 'using $LOADPATH (short option)' . -L foo __IN__ foo __OUT__ test_oE 'using $LOADPATH (long option)' . --autoload foo __IN__ foo __OUT__ test_oE 'dot script in subdirectory of $LOADPATH' . -L dir/bar __IN__ bar __OUT__ ) mkdir testpath/dir1 testpath/dir2 test_oE 'multiple directories in $LOADPATH' p="$PWD/testpath" YASH_LOADPATH="$p/dir1:$p/xxx:$p/dir:$p/dir2" . -L bar __IN__ bar __OUT__ ( chmod a+x print_args if command -v print_args >/dev/null 2>&1; then skip="true" fi chmod a-x print_args test_oE -e 0 'dot script not found in $PATH, falling back to $PWD, non-POSIX' set foo . print_args __IN__ foo __OUT__ ) ( posix=true test_Oe -e 1 'dot script not found in $PATH, no fallback, POSIX' PATH=$PWD/_no_such_directory_ set foo . print_args __IN__ .: file `print_args' was not found in $PATH __ERR__ #' #` ) ) ( posix='true' test_Oe -e n 'missing operand' . __IN__ .: this command requires an operand __ERR__ test_Oe -e n 'too many operands' . _no_such_file_ X __IN__ .: too many operands are specified __ERR__ test_Oe -e n 'invalid option' . -X '' __IN__ .: `-X' is not a valid option __ERR__ #' #` ) test_Oe -e n 'unset load path' unset YASH_LOADPATH . -L _no_such_file_ __IN__ .: file `_no_such_file_' was not found in $YASH_LOADPATH __ERR__ #' #` test_Oe -e n 'null load path' YASH_LOADPATH= . -L _no_such_file_ __IN__ .: file `_no_such_file_' was not found in $YASH_LOADPATH __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/echo-y.tst000066400000000000000000000074411354143602500155070ustar00rootroot00000000000000# echo-y.tst: yash-specific test of the echo built-in setup -d test_oE 'basic functionality of echo' echo 123 456 789 echo 1 22 '3 3' "4 4" 5\ 5 __IN__ 123 456 789 1 22 3 3 4 4 5 5 __OUT__ if ! testee -c 'command -bv echo' >/dev/null; then skip="true" fi test_n_ignored() { testcase "$1" "ECHO_STYLE=${ECHO_STYLE-} -n is ignored" \ 3<<\__IN__ 4<<\__OUT__ 5&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/enqueue.sh000066400000000000000000000060011354143602500155610ustar00rootroot00000000000000# enqueue.sh: runs a task sequentially # (C) 2015-2018 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # This script expects any number of (but usually one or more) operands. The # operands are considered as a command and arguments. The script enqueues the # command so that enqueued commands are run sequentially (not in parallel). # # The purpose of this script is to ensure that tests of job-control are run in # the foreground. Since at most one process group can be in the foreground, # those tests cannot be run in parallel. # # This script assumes all commands in the same queue are run in the same # working directory, with the same environment variables, etc. set -Ceu umask u+rwx if [ $# -eq 0 ]; then exit fi queue_dir="tmp.queue" created_queue_dir="false" tmp_file="tmp.$$" interrupted="" cleanup() { if "$created_queue_dir"; then until ! [ -d "$queue_dir" ] || rm -fr "$queue_dir"; do sleep 1; done fi rm -fr "$tmp_file" if [ "$interrupted" ]; then trap - "$interrupted" kill -s "$interrupted" $$ exit 1 fi } trap cleanup EXIT trap 'interrupted=${interrupted:-INT}' INT trap 'interrupted=${interrupted:-TERM}' TERM trap 'interrupted=${interrupted:-QUIT}' QUIT trap 'interrupted=${interrupted:-HUP}' HUP # Write the command in a temporary file and move it into the queue directory. # Don't make the file in the directory directly so that other instances of this # script don't see the file in an intermediate state. printf '%s\n' "$@" >|"$tmp_file" trial=0 n=0 until cmd_file="$queue_dir/$$.$n"; ln "$tmp_file" "$cmd_file" 2>/dev/null; do if [ -e "$cmd_file" ]; then n=$((n+1)) continue fi if mkdir "$queue_dir" 2>/dev/null; then created_queue_dir="true" elif ! [ -d "$queue_dir" ]; then trial=$((trial+1)) if [ "$trial" -gt 10 ]; then printf '%s: cannot create the queue directory.\n' "$0" >&2 exit 69 # sysexits.h EX_UNAVAILABLE fi fi done if ! "$created_queue_dir"; then # The queue directory has been created by another shell instance running # this script. That instance is responsible for running all enqueued # commands. exit fi IFS=' ' until [ "$interrupted" ] || rmdir "$queue_dir" 2>/dev/null; do if ! [ -d "$queue_dir" ]; then printf '%s: the queue directory was unexpectedly removed.\n' "$0" >&2 exit 69 # sysexits.h EX_UNAVAILABLE fi for file in "$queue_dir"/*; do $(cat "$file") rm "$file" done done # vim: set ts=8 sts=4 sw=4 noet: yash-2.49/tests/errexit-p.tst000066400000000000000000000156721354143602500162470ustar00rootroot00000000000000# errexit-p.tst: test of the errexit option for any POSIX-compliant shell posix="true" test_o -e 0 'noerrexit: successful simple command' true echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: successful simple command' -e true echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: failed simple command' false echo reached __IN__ reached __OUT__ test_O -e n 'errexit: failed simple command' -e false echo not reached __IN__ test_o -e 0 'noerrexit: independent redirection error' <_no_such_file_ echo reached __IN__ reached __OUT__ test_O -e n 'errexit: independent redirection error' -e <_no_such_file_ echo not reached __IN__ test_o -e 0 'noerrexit: redirection error on simple command' echo not printed <_no_such_file_ echo reached __IN__ reached __OUT__ test_O -e n 'errexit: redirection error on simple command' -e echo not printed <_no_such_file_ echo not reached __IN__ test_o -e 0 'noerrexit: middle of pipeline' false | false | true echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: middle of pipeline' -e false | false | true echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: last of pipeline' true | true | false echo reached __IN__ reached __OUT__ test_O -e n 'errexit: last of pipeline' -e true | true | false echo not reached __IN__ test_o -e 0 'noerrexit: negated pipeline' ! false | false | true ! true | true | false echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: negated pipeline' -e ! false | false | true ! true | true | false echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: initially failing and list' false && ! echo not reached && ! echo not reached echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: initially failing and list' -e false && ! echo not reached && ! echo not reached echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: finally failing and list' true && true && false echo reached __IN__ reached __OUT__ test_O -e n 'errexit: finally failing and list' -e true && true && false echo not reached __IN__ test_o -e 0 'noerrexit: all succeeding and list' true && true && true echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: all succeeding and list' -e true && true && true echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: initially succeeding or list' true || echo not reached || echo not reached echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: initially succeeding or list' -e true || echo not reached || echo not reached echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: finally succeeding or list' false || false || true echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: finally succeeding or list' -e false || false || true echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: all failing or list' false || false || false echo reached __IN__ reached __OUT__ test_O -e n 'errexit: all failing or list' -e false || false || false echo not reached __IN__ test_o -e 0 'noerrexit: subshell' (echo reached 1; false; echo reached 2) | cat (echo reached 3; false; echo reached 4) echo reached 5 __IN__ reached 1 reached 2 reached 3 reached 4 reached 5 __OUT__ test_o -e n 'errexit: subshell' -e (echo reached 1; false; echo not reached 2) | cat (echo reached 3; false; echo not reached 4) echo not reached 5 __IN__ reached 1 reached 3 __OUT__ test_o -e 0 'noerrexit: grouping' { echo reached 1; false; echo reached 2; } | cat { echo reached 3; false; echo reached 4; } echo reached 5 __IN__ reached 1 reached 2 reached 3 reached 4 reached 5 __OUT__ test_o -e n 'errexit: grouping' -e { echo reached 1; false; echo not reached 2; } | cat { echo reached 3; false; echo not reached 4; } echo not reached 5 __IN__ reached 1 reached 3 __OUT__ test_o -e 0 'noerrexit: for loop body' for i in 1 2 3; do echo a $i test $i -ne 2 echo b $i done echo reached __IN__ a 1 b 1 a 2 b 2 a 3 b 3 reached __OUT__ test_o -e n 'errexit: for loop body' -e for i in 1 2 3; do echo a $i test $i -ne 2 echo b $i done echo not reached __IN__ a 1 b 1 a 2 __OUT__ test_o -e 0 'noerrexit: case body' case a in a) echo reached 1 false echo reached 2 esac echo reached 3 __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e n 'errexit: case body' -e case a in a) echo reached 1 false echo not reached 2 esac echo not reached 3 __IN__ reached 1 __OUT__ test_o -e 0 'noerrexit: if condition' if false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o -e 0 'errexit: if condition' -e if false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o -e 0 'noerrexit: elif condition' if false; then : elif false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o -e 0 'errexit: elif condition' -e if false; then : elif false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o -e 0 'noerrexit: then body' if true; then echo reached 1; false; echo reached 2; fi echo reached 3 __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e n 'errexit: then body' -e if true; then echo reached 1; false; echo not reached 2; fi echo not reached 3 __IN__ reached 1 __OUT__ test_o -e 0 'noerrexit: else body' if false; then :; else echo reached 1; false; echo reached 2; fi echo reached 3 __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e n 'errexit: else body' -e if false; then :; else echo reached 1; false; echo not reached 2; fi echo not reached 3 __IN__ reached 1 __OUT__ test_o -e 0 'noerrexit: while condition' while false; do : done echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: while condition' -e while false; do : done echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: while body' while true; do echo reached 1 false echo reached 2 break done echo reached 3 __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e n 'errexit: while body' -e while true; do echo reached 1 false echo not reached 2 break done echo not reached 3 __IN__ reached 1 __OUT__ test_o -e 0 'noerrexit: until condition' until false; true; do : done echo reached __IN__ reached __OUT__ test_o -e 0 'errexit: until condition' -e until false; true; do : done echo reached __IN__ reached __OUT__ test_o -e 0 'noerrexit: until body' until false; do echo reached 1 false echo reached 2 break done echo reached 3 __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e n 'errexit: until body' -e until false; do echo reached 1 false echo reached 2 break done echo reached 3 __IN__ reached 1 __OUT__ test_O -e n 'ignored failure in subshell' -e ( false && true; ) echo not reached __IN__ test_o -e 0 'ignored failure in grouping' -e { false && true; } echo reached __IN__ reached __OUT__ test_o -e 0 'ignored failure in if body' -e if true; then false && true; fi echo reached __IN__ reached __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/errexit-y.tst000066400000000000000000000033261354143602500162510ustar00rootroot00000000000000# errexit-y.tst: yash-specific test of the errexit option # I think the shell should exit for all cases below, but POSIX and existing # implementations vary... # An expansion error in a non-interactive shell causes immediate exit of the # shell (regardless of errexit), so expansion errors should be tested in an # interactive shell. setup 'set -e' test_O -e n 'expansion error in case word' -i +m case ${a?} in (*) esac echo not reached __IN__ test_O -e n 'expansion error in case pattern' -i +m case a in (${a?}) esac echo not reached __IN__ test_O -e n 'expansion error in for word' -i +m for i in ${a?}; do echo not reached; done echo not reached __IN__ test_O -e n 'redirection error on subshell' ( :; ) <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on grouping' { :; } <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on for loop' for i in i; do :; done <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on case' case i in esac <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on if' if :; then :; fi <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on while loop' while echo not reached; false; do :; done <_no_such_file_ echo not reached __IN__ test_O -e n 'redirection error on until loop' until echo not reached; do :; done <_no_such_file_ echo not reached __IN__ ( if ! testee -c 'command -v [[' >/dev/null; then skip="true" fi test_O -e 2 'expansion error in double-bracket command' -i +m [[ ${a?} ]] echo not reached __IN__ test_O -e 2 'redirection error on double-bracket command' exec 3>&1 [[ $(echo not reached >&3) ]] <_no_such_file_ echo not reached __IN__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/error-p.tst000066400000000000000000000230141354143602500157030ustar00rootroot00000000000000# error-p.tst: test of error conditions for any POSIX-compliant shell posix="true" test_O -d -e n 'syntax error kills non-interactive shell' fi echo not reached __IN__ test_O -d -e n 'syntax error in eval kills non-interactive shell' eval fi echo not reached __IN__ test_o -d 'syntax error in subshell' (eval fi; echo not reached) [ $? -ne 0 ] echo $? __IN__ 0 __OUT__ test_o -d 'syntax error spares interactive shell' -i +m fi echo reached __IN__ reached __OUT__ test_O -d -e n 'expansion error kills non-interactive shell' unset a echo ${a?} echo not reached __IN__ test_o -d 'expansion error in subshell' unset a (echo ${a?}; echo not reached) [ $? -ne 0 ] echo $? __IN__ 0 __OUT__ test_o -d 'expansion error spares interactive shell' -i +m unset a echo ${a?} [ $? -ne 0 ] echo $? __IN__ 0 __OUT__ test_O -d -e 127 'command not found' ./_no_such_command_ __IN__ ############################################################################### test_O 'assignment error without command kills non-interactive shell' readonly a=a a=b printf 'not reached\n' __IN__ test_o 'assignment error without command in subshell' readonly a=a (a=b; printf 'not reached\n') [ $? -ne 0 ] echo $? __IN__ 0 __OUT__ test_o 'assignment error without command spares interactive shell' -i +m readonly a=a a=b printf 'reached\n' __IN__ reached __OUT__ # $1 = line no. # $2 = command name test_assign() { testcase "$1" -d \ "assignment error on command $2 kills non-interactive shell" \ 3<<__IN__ 4&1 | head -n 1 __IN__ read: option `--p' is ambiguous __OUT__ #` ############################################################################### # $1 = line no. # $2 = built-in name test_special_builtin_syntax_i() { testcase "$1" -d \ "argument syntax error on special built-in $2 spares interactive shell${posix:+" (POSIX)"}" \ -i +m 3<<__IN__ 4<<\__OUT__ $2 --no-such-option-- echo \$? __IN__ 2 __OUT__ } # $1 = line no. # $2 = built-in name test_nonspecial_builtin_syntax() ( if ! testee -c "set +o posix; command -bv $2" >/dev/null; then skip="true" fi testcase "$1" -d \ "argument syntax error on non-special built-in $2 spares shell${posix:+" (POSIX)"}" \ 3<<__IN__ 4<<\__OUT__ # -l and -n are mutually exclusive for the kill built-in. # Four arguments are too many for the test built-in. $2 -l -n --no-such-option-- - echo \$? __IN__ 2 __OUT__ ) # $1 = line no. # $2 = built-in name test_nonspecial_builtin_redirect() { testcase "$1" -d \ "redirection error on non-special built-in $2 spares shell${posix:+" (POSIX)"}" \ 3<<__IN__ 4<<\__OUT__ $2 <_no_such_file_ echo \$? __IN__ 2 __OUT__ } ( posix="true" # $1 = line no. # $2 = built-in name test_special_builtin_syntax() { testcase "$1" -d \ "argument syntax error on special built-in $2 kills non-interactive shell (POSIX)" \ 3<<__IN__ 4 dot1 <<__END__ false echo echo __END__ test_o 'errreturn: negated dot builtin call' -o errreturn ! . ./dot1 echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: negated dot builtin call' -e ! . ./dot1 __IN__ echo __OUT__ test_o 'errreturn: independent redirection error' -o errreturn ! ( <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: independent redirection error' -e ! ( <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on simple command' -o errreturn ! ( echo not printed <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on simple command' -e ! ( echo not printed <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on subshell' -o errreturn ! ( ( :; ) <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on subshell' -e ! ( ( :; ) <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on grouping' -o errreturn ! ( { :; } <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on grouping' -e ! ( { :; } <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on for loop' -o errreturn ! ( for i in i; do :; done <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on for loop' -e ! ( for i in i; do :; done <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on case' -o errreturn ! ( case i in esac <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on case' -e ! ( case i in esac <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on if' -o errreturn ! ( if :; then :; fi <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on if' -e ! ( if :; then :; fi <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on while loop' -o errreturn ! ( while echo not reached 1; false; do :; done <_no_such_file_ echo not reached 2 ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on while loop' -e ! ( while echo not reached; false; do :; done <_no_such_file_ echo reached ) __IN__ reached __OUT__ test_o 'errreturn: redirection error on until loop' -o errreturn ! ( until echo not reached; do :; done <_no_such_file_; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: redirection error on until loop' -e ! ( until echo not reached; do :; done <_no_such_file_; echo reached; ) __IN__ reached __OUT__ test_o -e 0 'errreturn: middle of pipeline' -o errreturn false | false | true echo reached __IN__ reached __OUT__ test_o 'errreturn: last of pipeline' -o errreturn ! ( true | true | false; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: last of pipeline' -e ! ( true | true | false; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: negated pipeline' -o errreturn ! false | false | true ! true | true | false echo reached $? __IN__ reached 0 __OUT__ test_o -e 0 'errreturn: initially failing and list' -o errreturn false && ! echo not reached && ! echo not reached echo reached __IN__ reached __OUT__ test_o 'errreturn: finally failing and list' -o errreturn ! ( true && true && false; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: finally failing and list' -e ! ( true && true && false; echo reached; ) __IN__ reached __OUT__ test_o -e 0 'errreturn: all succeeding and list' -o errreturn true && true && true echo reached __IN__ reached __OUT__ test_o -e 0 'errreturn: initially succeeding or list' -o errreturn true || echo not reached || echo not reached echo reached __IN__ reached __OUT__ test_o -e 0 'errreturn: finally succeeding or list' -o errreturn false || false || true echo reached __IN__ reached __OUT__ test_o 'errreturn: all failing or list' -o errreturn ! ( false || false || false; echo not reached; ) echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: all failing or list' -e ! ( false || false || false; echo reached; ) __IN__ reached __OUT__ test_o 'errreturn: subshell' -o errreturn ! ( (echo reached 1; false; echo not reached 2) | cat (echo reached 3; false; echo not reached 4) echo not reached 5 ) echo reached 6 $? __IN__ reached 1 reached 3 reached 6 0 __OUT__ test_o -e n 'errexit: subshell' -e ! ( (echo reached 1; false; echo reached 2) | cat (echo reached 3; false; echo reached 4) echo reached 5 ) __IN__ reached 1 reached 2 reached 3 reached 4 reached 5 __OUT__ test_o 'errreturn: grouping' -o errreturn ! ( { echo reached 1; false; echo not reached 2; } | cat { echo reached 3; false; echo not reached 4; } echo not reached 5 ) echo reached 6 $? __IN__ reached 1 reached 3 reached 6 0 __OUT__ test_o -e n 'errexit: grouping' -e ! ( { echo reached 1; false; echo reached 2; } | cat { echo reached 3; false; echo reached 4; } echo reached 5 ) __IN__ reached 1 reached 2 reached 3 reached 4 reached 5 __OUT__ test_o 'errreturn: expansion error in for word' -i +m -o errreturn f() { for i in ${a?}; do echo not reached 1; done; echo not reached 2; } ! f echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: expansion error in for word' -i +m -e f() { for i in ${a?}; do echo not reached; done; echo reached; } ! f __IN__ reached __OUT__ test_o 'errreturn: for loop body' -o errreturn ! ( for i in 1 2 3; do echo a $i test $i -ne 2 echo b $i done echo not reached ) echo reached $? __IN__ a 1 b 1 a 2 reached 0 __OUT__ test_o -e n 'errexit: for loop body' -e ! ( for i in 1 2 3; do echo a $i test $i -ne 2 echo b $i done echo reached ) __IN__ a 1 b 1 a 2 b 2 a 3 b 3 reached __OUT__ test_o 'errreturn: expansion error in case word' -i +m -o errreturn f() { case ${a?} in (*) esac; echo not reached; } ! f echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: expansion error in case word' -i +m -e f() { case ${a?} in (*) esac; echo reached; } ! f __IN__ reached __OUT__ test_o 'errreturn: expansion error in case pattern' -i +m -o errreturn f() { case a in (${a?}) esac; echo not reached; } ! f echo reached $? __IN__ reached 0 __OUT__ test_o -e n 'errexit: expansion error in case pattern' -i +m -e f() { case a in (${a?}) esac; echo reached; } ! f __IN__ reached __OUT__ test_o 'errreturn: case body' -o errreturn ! ( case a in (a) echo reached 1 false echo not reached 2 esac echo not reached 3 ) echo reached 4 $? __IN__ reached 1 reached 4 0 __OUT__ test_o -e n 'errexit: case body' -e ! ( case a in (a) echo reached 1 false echo reached 2 esac echo reached 3 ) __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e 0 'errreturn: if condition' -o errreturn if false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o -e 0 'errreturn: function call inside if condition' -o errreturn f() { false; echo not reached $1; } if f 1; then echo failed 1; else echo passed 1; fi if f 2 || echo passed 2; then :; fi __IN__ passed 1 passed 2 __OUT__ test_o -e 0 'errexit: function call inside if condition' -e f() { false; true; } if f 1; then echo passed 1; else echo failed 1; fi if f 2 || echo failed 2; then echo passed 2; fi __IN__ passed 1 passed 2 __OUT__ test_o -e 0 'errreturn: subshell inside if condition' -o errreturn if (false; echo not reached 1); then echo failed 1; else echo passed 1; fi if (false; echo not reached 2) || echo passed 2; then :; fi __IN__ passed 1 passed 2 __OUT__ test_o -e 0 'errexit: subshell inside if condition' -e if (false; true); then echo passed 1; else echo failed 1; fi if (false; true) || echo failed 2; then echo passed 2; fi __IN__ passed 1 passed 2 __OUT__ test_o -e 0 'errreturn: elif condition' -o errreturn if false; then : elif false; true; then echo reached 1 else echo not reached fi echo reached 2 __IN__ reached 1 reached 2 __OUT__ test_o 'errreturn: then body' -o errreturn ! ( if true; then echo reached 1; false; echo not reached 2; fi echo not reached 3 ) echo reached 4 $? __IN__ reached 1 reached 4 0 __OUT__ test_o -e n 'errexit: then body' -e ! ( if true; then echo reached 1; false; echo reached 2; fi echo reached 3 ) __IN__ reached 1 reached 2 reached 3 __OUT__ test_o 'errreturn: else body' -o errreturn ! ( if false; then :; else echo reached 1; false; echo not reached 2; fi echo not reached 3 ) echo reached 4 $? __IN__ reached 1 reached 4 0 __OUT__ test_o -e n 'errexit: else body' -e ! ( if false; then :; else echo reached 1; false; echo reached 2; fi echo reached 3 ) __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e 0 'errreturn: while condition' -o errreturn while false; do : done echo reached __IN__ reached __OUT__ test_o 'errreturn: while body' -o errreturn ! ( while true; do echo reached 1 false echo not reached 2 break done echo not reached 3 ) echo reached 4 $? __IN__ reached 1 reached 4 0 __OUT__ test_o -e n 'errexit: while body' -e ! ( while true; do echo reached 1 false echo reached 2 break done echo reached 3 ) __IN__ reached 1 reached 2 reached 3 __OUT__ test_o -e 0 'errreturn: until condition' -o errreturn until false; true; do : done echo reached __IN__ reached __OUT__ test_o 'errreturn: until body' -o errreturn ! ( until false; do echo reached 1 false echo not reached 2 break done echo not reached 3 ) echo reached 4 $? __IN__ reached 1 reached 4 0 __OUT__ test_o -e n 'errexit: until body' -e ! ( until false; do echo reached 1 false echo reached 2 break done echo reached 3 ) __IN__ reached 1 reached 2 reached 3 __OUT__ test_o 'no return in interactive shell' -i +m -o errreturn false echo reached __IN__ reached __OUT__ test_O -e n 'errexit supersedes errreturn' -i +m -e -o errreturn false echo not reached __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/eval-p.tst000066400000000000000000000012471354143602500155050ustar00rootroot00000000000000# eval-p.tst: test of the eval built-in for any POSIX-compliant shell posix="true" test_OE -e 0 'evaluating no operands' false eval __IN__ test_OE -e 0 'evaluating null operands' false eval '' '' '' __IN__ test_oE -e 0 'evaluating some commands' eval 'echo foo; echo bar' __IN__ foo bar __OUT__ test_oE -e 0 'operands are concatenated with spaces in-between' eval 'echo foo' 'echo bar' eval 'echo 1"' '' '"2' __IN__ foo echo bar 1 2 __OUT__ test_OE -e 23 'exit status of evaluation' eval '(exit 23)' __IN__ test_oE -e 0 'effect on environment in evaluation' a=foo eval 'a=bar' echo $a eval exit echo not reached __IN__ bar __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/eval-y.tst000066400000000000000000000017511354143602500155160ustar00rootroot00000000000000# eval-y.tst: yash-specific test of the eval built-in test_OE -e 0 'empty iteration' false eval -i __IN__ test_oE -e 0 'single iteration' eval -i 'echo foo; echo bar' __IN__ foo bar __OUT__ test_OE -e 0 'single empty iteration' false eval -i '' __IN__ test_oE -e 0 'multiple iteration' eval -i 'echo foo; echo bar' 'echo 1; echo 2' 'echo a; echo b' __IN__ foo bar 1 2 a b __OUT__ test_oE -e 0 'iteration with long option' eval --iteration 'echo foo; echo bar' 'echo 1; echo 2' 'echo a; echo b' __IN__ foo bar 1 2 a b __OUT__ test_oE -e 13 'exit status in iteration' (exit 7) eval -i 'echo a $?; (exit 11)' 'echo b $?; (exit 12)' 'echo c $?; (exit 13)' __IN__ a 7 b 7 c 7 __OUT__ test_oE -e 0 'effect on environment in iteration' eval -i 'a=foo' echo $a eval -i 'a=${a}bar' 'echo $a' exit 'echo not reached' __IN__ foo foobar __OUT__ test_Oe -e n 'invalid option' eval --no-such-option __IN__ eval: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/exec-p.tst000066400000000000000000000022201354143602500154720ustar00rootroot00000000000000# exec-p.tst: test of the exec built-in for any POSIX-compliant shell posix="true" ( setup 'set -e' test_oE 'exec without arguments' exec echo reached __IN__ reached __OUT__ test_Oe 'exec with redirections' exec >&2 2>/dev/null echo reached ./_no_such_command_ __IN__ reached __ERR__ test_Oe -e n 'exec with redirections in grouping' { exec 4>&3; } 3>&2 echo foo >&4 { exec >&3; } 2>/dev/null __IN__ foo __ERR__ ) test_oE -e 0 'executing external command' exec echo foo bar echo not reached __IN__ foo bar __OUT__ test_OE -e 0 'executing external command with option' exec cat -u /dev/null __IN__ test_OE -e 0 'process ID of executed process' exec sh -c "[ \$\$ -eq $$ ]" __IN__ test_oE 'exec in subshell' (exec echo foo bar) echo $? __IN__ foo bar 0 __OUT__ test_O -d -e 127 'executing non-existing command (relative)' exec ./_no_such_command_ echo not reached __IN__ test_O -d -e 127 'executing non-existing command (empty path)' PATH= exec _no_such_command_ echo not reached __IN__ test_x -d -e 0 'redirection error on exec' command exec <_no_such_file_ status=$? [ 0 -lt $status ] && [ $status -le 125 ] __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/exec-y.tst000066400000000000000000000044661354143602500155210ustar00rootroot00000000000000# exec-y.tst: yash-specific test of the exec built-in test_O 'execution error kills shell, POSIX, interactive' --posix -i +m exec ./_no_such_command_ echo not reached __IN__ test_O 'execution error kills shell, non-POSIX, non-interactive' exec ./_no_such_command_ echo not reached __IN__ test_o 'execution error spares shell, non-POSIX, interactive' -i +m exec ./_no_such_command_ echo reached __IN__ reached __OUT__ test_oE -e 0 'executing with specific name (-a)' exec -a foo sh -c 'echo "$0"' echo not reached __IN__ foo __OUT__ test_oE -e 0 'executing with specific name (--as)' exec --as=foo sh -c 'echo "$0"' echo not reached __IN__ foo __OUT__ # This test fails on some environments, notably Cygwin, which implicitly adds # some environment variables on exec'ing. #test_OE -e 0 'clearing environment variables (-c)' #exec -c env #__IN__ ( # We need an absolute path to the "env" command because it cannot be found with # $PATH cleared. export ENVCMD="$(command -v env)" test_OE -e 0 'clearing environment variables (-c)' "$ENVCMD" -i "$ENVCMD" | sort >1.expected (exec -c "$ENVCMD") | sort >1.actual diff 1.expected 1.actual __IN__ test_OE -e 0 'clearing environment variables (--clear)' "$ENVCMD" -i "$ENVCMD" | sort >2.expected (exec --clear "$ENVCMD") | sort >2.actual diff 2.expected 2.actual __IN__ test_OE -e 0 'clearing and adding scalar environment variables' "$ENVCMD" -i FOO=1 BAR=2 "$ENVCMD" | sort >3.expected (FOO=1 BAR=2 exec -c "$ENVCMD") | sort >3.actual diff 3.expected 3.actual __IN__ test_OE -e 0 'clearing and adding array environment variables' "$ENVCMD" -i FOO=1:2:3 BAR=abc:xyz "$ENVCMD" | sort >4.expected (FOO=(1 2 3) BAR=(abc xyz) exec -c "$ENVCMD") | sort >4.actual diff 4.expected 4.actual __IN__ ) # The -f (--force) option is not tested because it cannot be tested without # producing garbage processes. ( posix="true" test_Oe -e n 'invalid option' exec -a echo echo echo __IN__ exec: `-a' is not a valid option __ERR__ #' #` test_Oe -e n 'invalid option' exec -c echo echo echo __IN__ exec: `-c' is not a valid option __ERR__ #' #` test_Oe -e n 'invalid option' exec -f echo echo echo __IN__ exec: `-f' is not a valid option __ERR__ #' #` ) test_Oe -e 2 'invalid option' exec --no-such-option __IN__ exec: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/exit-p.tst000066400000000000000000000027031354143602500155250ustar00rootroot00000000000000# exit-p.tst: test of the exit built-in for any POSIX-compliant shell posix="true" test_OE -e 0 'exiting with 0' false exit 0 __IN__ test_OE -e 17 'exiting with 17' exit 17 __IN__ test_OE -e 19 'exiting with 19 in subshell' (exit 19) __IN__ test_OE -e 0 'default exit status without previous command' exit __IN__ test_OE -e 0 'default exit status with previous succeeding command' true exit __IN__ test_OE -e 5 'default exit status with previous failing command' (exit 5) exit __IN__ test_OE -e 3 'default exit status in subshell' (exit 3) (exit) __IN__ test_oE -e 19 'exiting with trap' trap 'echo TRAP' EXIT exit 19 __IN__ TRAP __OUT__ test_OE -e 1 'exit status with trap' trap '(exit 2)' EXIT (exit 1) exit __IN__ test_OE -e 0 'exiting from trap with 0' trap 'exit 0' EXIT exit 1 __IN__ test_OE -e 7 'exiting from trap with 7' trap 'exit 7' EXIT exit 1 __IN__ test_OE -e 2 'default exit status in trap in exiting with default' trap exit EXIT (exit 2) exit __IN__ test_OE -e 2 \ 'default exit status with previous command in trap in exiting with default' trap '(exit 1); exit' EXIT (exit 2) exit __IN__ # POSIX says the exit status in this case should be that of "the command that # executed immediately preceding the trap action." Many shells including yash # interprets it as the exit status of "exit" rather than "trap." test_OE -e 1 'default exit status in trap in exiting with 1' trap exit EXIT exit 1 __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/exit-y.tst000066400000000000000000000010751354143602500155370ustar00rootroot00000000000000# exit-y.tst: yash-specific test of the exit built-in test_Oe -e 2 'too many operands' exit 1 2 __IN__ exit: too many operands are specified __ERR__ test_Oe -e 2 'invalid operand: not a integer' exit x echo not reached __IN__ exit: `x' is not a valid integer __ERR__ #' #` test_Oe -e 2 'invalid operand: negative integer' exit -- -100 echo not reached __IN__ exit: `-100' is not a valid integer __ERR__ #' #` test_Oe -e 2 'invalid option' exit --no-such-option __IN__ exit: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/export-p.tst000066400000000000000000000014041354143602500160720ustar00rootroot00000000000000# export-p.tst: test of the export built-in for any POSIX-compliant shell posix="true" test_oE -e 0 'exporting one variable' -e export a=bar echo 1 $a sh -c 'echo 2 $a' __IN__ 1 bar 2 bar __OUT__ test_oE -e 0 'exporting many variables' -e a=X b=B c=X export a=A b c=C echo 1 $a $b $c sh -c 'echo 2 $a $b $c' __IN__ 1 A B C 2 A B C __OUT__ test_oE -e 0 'reusing printed exported variables' export a=A e="$(export -p)" unset a a=X eval "$e" sh -c 'echo $a' __IN__ A __OUT__ test_oE 'exporting with assignments' a=A export b=B echo $a sh -c 'echo $b' __IN__ A B __OUT__ test_O -d -e n 'read-only variable cannot be re-assigned' readonly a=1 export a=2 echo not reached # special built-in error kills non-interactive shell __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/export-y.tst000066400000000000000000000030431354143602500161040ustar00rootroot00000000000000# export-y.tst: yash-specific test of the export built-in # XXX: missing test 'printing all exported variables' test_oE -e 0 'printing specific exported variables' export a=A f=FOO export -p a f __IN__ export a=A export f=FOO __OUT__ test_OE -e 0 'without argument, -p is assumed' export >withoutp.out export -p >withp.out diff withoutp.out withp.out __IN__ test_oE -e 0 'assigning empty value' export a= export -p a __IN__ export a='' __OUT__ test_oE 'exporting with -p' export -p a=A export -p a __IN__ export a=A __OUT__ test_oE 'un-exporting' export a=A export -X a sh -c 'echo ${a-unset}' __IN__ unset __OUT__ test_Oe -e 1 'assigning to read-only variable' readonly a=A export a=X __IN__ export: $a is read-only __ERR__ test_oE 'exporting before separate assignment' export a a=A sh -c 'echo $a' __IN__ A __OUT__ test_O -d -e 1 'assigning to ill-named variable' export =A __IN__ ( posix="true" test_Oe -e 2 'invalid option -r (POSIX)' export -r __IN__ export: `-r' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option -X (POSIX)' export -X __IN__ export: `-X' is not a valid option __ERR__ #' #` ) test_Oe -e 2 'invalid option -z' export -z __IN__ export: `-z' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' export --no-such=option __IN__ export: `--no-such=option' is not a valid option __ERR__ #' #` test_O -d -e 1 'printing to closed stream' export >&- __IN__ test_Oe -e 1 'printing non-existing variable' unset a export -p a __IN__ export: no such variable $a __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/fc-y.tst000066400000000000000000000370621354143602500151630ustar00rootroot00000000000000# fc-y.tst: yash-specific test of the fc built-in # Although the fc built-in is defined in POSIX, many aspects of its behavior # are implementation-specific. That's why we don't have the fc-p.tst file. if ! testee -c 'command -bv fc' >/dev/null; then skip="true" fi cat >rcfile1 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile HISTSIZE=$histsize unset HISTRMDUP __END__ cat >rcfile2 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile unset HISTRMDUP HISTSIZE __END__ test_oE -e 0 'fc is a semi-special built-in' command -V fc __IN__ fc: a semi-special built-in __OUT__ ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 __END__ test_oE -e 0 'listing commands in history (-l)' -i +m --rcfile="rcfile1" fc -l __IN__ 1 echo foo 1 2 echo foo 2 3 echo foo 3 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 __END__ test_oE -e 0 'listing from specified command (-l)' -i +m --rcfile="rcfile1" fc -l 2 __IN__ 2 echo foo 2 3 echo foo 3 4 fc -l 2 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 __END__ test_oE -e 0 'listing range of commands, single (-l)' -i +m --rcfile="rcfile1" fc -l 6 __IN__ 6 fc -l 6 __OUT__ test_oE -e 0 'listing range of commands, many (-l)' -i +m --rcfile="rcfile1" fc -l 2 4 __IN__ 2 echo foo 2 3 echo foo 3 4 echo foo 4 __OUT__ test_oE -e 0 'listing descending range of commands (-l)' \ -i +m --rcfile="rcfile1" fc -l 2 1 __IN__ 2 echo foo 2 1 echo foo 1 __OUT__ test_oE -e 0 'listing range of commands, reverse (-lr)' -i +m --rcfile="rcfile1" fc -lr 2 4 __IN__ 4 echo foo 4 3 echo foo 3 2 echo foo 2 __OUT__ test_oE -e 0 'listing descending range of commands, reverse (-lr)' \ -i +m --rcfile="rcfile1" fc -lr 4 2 __IN__ 2 echo foo 2 3 echo foo 3 4 echo foo 4 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 12 echo foo 13 echo foo 14 echo foo 15 __END__ test_oE -e 0 'at most 16 commands are listed by default (-l)' \ -i +m --rcfile="rcfile1" fc -l echo ---; fc -l __IN__ 1 echo foo 1 2 echo foo 2 3 echo foo 3 4 echo foo 4 5 echo foo 5 6 echo foo 6 7 echo foo 7 8 echo foo 8 9 echo foo 9 10 echo foo 10 11 echo foo 11 12 echo foo 12 13 echo foo 13 14 echo foo 14 15 echo foo 15 16 fc -l --- 2 echo foo 2 3 echo foo 3 4 echo foo 4 5 echo foo 5 6 echo foo 6 7 echo foo 7 8 echo foo 8 9 echo foo 9 10 echo foo 10 11 echo foo 11 12 echo foo 12 13 echo foo 13 14 echo foo 14 15 echo foo 15 16 fc -l 17 echo ---; fc -l __OUT__ test_oE -e 0 'at most 16 commands are listed by default, reverse (-lr)' \ -i +m --rcfile="rcfile1" fc -lr __IN__ 18 fc -lr 17 echo ---; fc -l 16 fc -l 15 echo foo 15 14 echo foo 14 13 echo foo 13 12 echo foo 12 11 echo foo 11 10 echo foo 10 9 echo foo 9 8 echo foo 8 7 echo foo 7 6 echo foo 6 5 echo foo 5 4 echo foo 4 3 echo foo 3 __OUT__ test_oE -e 0 'listing without numbers (-lr)' -i +m --rcfile="rcfile1" fc -ln 10 12 __IN__ echo foo 10 echo foo 11 echo foo 12 __OUT__ ) # Test of the -v option is missing because we cannot control time. ( export histfile=histfile$LINENO histsize=5 test_oE 'old entries are removed' -i +m --rcfile="rcfile1" echo foo 1 echo foo 2 fc -l echo foo 4 fc -l 1 6 fc -l 1 7 fc -l 1 8 __IN__ foo 1 foo 2 1 echo foo 1 2 echo foo 2 3 fc -l foo 4 1 echo foo 1 2 echo foo 2 3 fc -l 4 echo foo 4 5 fc -l 1 6 2 echo foo 2 3 fc -l 4 echo foo 4 5 fc -l 1 6 6 fc -l 1 7 3 fc -l 4 echo foo 4 5 fc -l 1 6 6 fc -l 1 7 7 fc -l 1 8 __OUT__ export histsize=1 test_oE 'HISTSIZE=1' -i +m --rcfile="rcfile1" fc -l __IN__ 2 fc -l __OUT__ test_oE 'out-of-range history numbers are clamped, positive' \ -i +m --rcfile="rcfile1" fc -l 3 100 __IN__ 2 fc -l 3 100 __OUT__ export histsize=0 test_oE 'HISTSIZE=0 is equal to HISTSIZE=1' -i +m --rcfile="rcfile1" fc -l __IN__ 2 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 __END__ test_oE 'listing with negative index (-l)' -i +m --rcfile="rcfile1" fc -l -5 -3 __IN__ 2 echo foo 2 3 echo foo 3 4 echo foo 4 __OUT__ test_oE 'out-of-range history numbers are clamped, negative' \ -i +m --rcfile="rcfile1" fc -l -100 -100 __IN__ 1 echo foo 1 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 : echo foo 3 : X __END__ test_oE 'identifying command by prefix' -i +m --rcfile="rcfile1" fc -s ech __IN__ echo foo 2 foo 2 __OUT__ test_Oe -e 1 'prefix matching no command' -i +m --rcfile="rcfile1" fc -s XXX __IN__ fc: no such history entry beginning with `XXX' __ERR__ #` ) ( export histfile=histfile$LINENO test_oE 'default HISTSIZE is >= 128' -i +m --rcfile="rcfile1" : foo 1 : foo 2 : foo 3 : foo 4 : foo 5 : foo 6 : foo 7 : foo 8 : foo 9 : foo 10 : foo 11 : foo 12 : foo 13 : foo 14 : foo 15 : foo 16 : foo 17 : foo 18 : foo 19 : foo 20 : foo 21 : foo 22 : foo 23 : foo 24 : foo 25 : foo 26 : foo 27 : foo 28 : foo 29 : foo 30 : foo 31 : foo 32 : foo 33 : foo 34 : foo 35 : foo 36 : foo 37 : foo 38 : foo 39 : foo 40 : foo 41 : foo 42 : foo 43 : foo 44 : foo 45 : foo 46 : foo 47 : foo 48 : foo 49 : foo 50 : foo 51 : foo 52 : foo 53 : foo 54 : foo 55 : foo 56 : foo 57 : foo 58 : foo 59 : foo 60 : foo 61 : foo 62 : foo 63 : foo 64 : foo 65 : foo 66 : foo 67 : foo 68 : foo 69 : foo 70 : foo 71 : foo 72 : foo 73 : foo 74 : foo 75 : foo 76 : foo 77 : foo 78 : foo 79 : foo 80 : foo 81 : foo 82 : foo 83 : foo 84 : foo 85 : foo 86 : foo 87 : foo 88 : foo 89 : foo 90 : foo 91 : foo 92 : foo 93 : foo 94 : foo 95 : foo 96 : foo 97 : foo 98 : foo 99 : foo 100 : foo 101 : foo 102 : foo 103 : foo 104 : foo 105 : foo 106 : foo 107 : foo 108 : foo 109 : foo 110 : foo 111 : foo 112 : foo 113 : foo 114 : foo 115 : foo 116 : foo 117 : foo 118 : foo 119 : foo 120 : foo 121 : foo 122 : foo 123 : foo 124 : foo 125 : foo 126 : foo 127 fc -l 1 3; fc -l 128 __IN__ 1 : foo 1 2 : foo 2 3 : foo 3 128 fc -l 1 3; fc -l 128 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # This is yash-specific behavior; POSIX allows a history entry to have more # than one line. test_oE 'multi-line command' -i +m --rcfile="rcfile1" echo \ foo fc -l __IN__ foo 1 echo \ 2 foo 3 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 __END__ test_oE 're-executing command w/o editing (-s)' -i +m --rcfile="rcfile1" fc -s 1 __IN__ echo foo 1 foo 1 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 __END__ test_oE 'default operand for -s is -1' -i +m --rcfile="rcfile1" fc -s __IN__ echo foo 1 foo 1 __OUT__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo/bar/foo/bar __END__ test_oE 'replacing part of re-executed command, default command (-s)' \ -i +m --rcfile="rcfile1" fc -s oo=xx fc -s 1=2 # no match, no replacement __IN__ echo fxx/bar/foo/bar fxx/bar/foo/bar echo fxx/bar/foo/bar fxx/bar/foo/bar __OUT__ test_oE 'replacing part of re-executed command, non-default command (-s)' \ -i +m --rcfile="rcfile1" fc -s oo=zz 1 __IN__ echo fzz/bar/foo/bar fzz/bar/foo/bar __OUT__ ) ( export histfile=histfile$LINENO histsize=50 if ! [ "${skip-}" ]; then # Prepare the first history entries w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 fc -s 1=2 __END__ fi test_oE 're-executed command is saved in history (-s)' -i +m --rcfile="rcfile1" fc -l __IN__ 1 echo foo 1 2 echo foo 2 3 fc -l __OUT__ test_oE 'suppressing reprinting of re-executed command (-qs)' \ -i +m --rcfile="rcfile1" fc -qs 1 __IN__ foo 1 __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 12 echo foo 13 echo foo 14 __END__ test_oE 're-executing single command after editing' -i +m --rcfile="rcfile1" FCEDIT=true fc 2 __IN__ echo foo 2 foo 2 __OUT__ test_oE 're-executing range of commands after editing' -i +m --rcfile="rcfile1" FCEDIT=true fc 2 4 __IN__ echo foo 2 echo foo 3 echo foo 4 foo 2 foo 3 foo 4 __OUT__ test_oE 're-executing descending range of commands after editing' \ -i +m --rcfile="rcfile1" FCEDIT=true fc 4 2 __IN__ echo foo 4 echo foo 3 echo foo 2 foo 4 foo 3 foo 2 __OUT__ test_oE 're-executing reverse range of commands after editing' \ -i +m --rcfile="rcfile1" FCEDIT=true fc -r 2 4 __IN__ echo foo 4 echo foo 3 echo foo 2 foo 4 foo 3 foo 2 __OUT__ test_oE 're-executing reverse descending range of commands after editing' \ -i +m --rcfile="rcfile1" FCEDIT=true fc -r 4 2 __IN__ echo foo 2 echo foo 3 echo foo 4 foo 2 foo 3 foo 4 __OUT__ test_oE 'last command is edited by default (w/o -r)' -i +m --rcfile="rcfile1" echo foo X FCEDIT=true fc __IN__ foo X echo foo X foo X __OUT__ test_oE 'last command is edited by default (with -r)' -i +m --rcfile="rcfile1" echo foo X FCEDIT=true fc -r __IN__ foo X echo foo X foo X __OUT__ test_oE 'suppressing reprinting of re-executed command (-q)' \ -i +m --rcfile="rcfile1" FCEDIT=true fc -q 1 __IN__ foo 1 __OUT__ # The first 10 lines of the edited commands are first printed by "head". # Then "fc" prints the commands before executing. test_oE '$FCEDIT specifies editor' -i +m --rcfile="rcfile1" FCEDIT=head fc 2 13 __IN__ echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 12 echo foo 13 foo 2 foo 3 foo 4 foo 5 foo 6 foo 7 foo 8 foo 9 foo 10 foo 11 foo 12 foo 13 __OUT__ test_oE 'option overrides $FCEDIT' -i +m --rcfile="rcfile1" FCEDIT=true fc -e head 2 13 __IN__ echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 12 echo foo 13 foo 2 foo 3 foo 4 foo 5 foo 6 foo 7 foo 8 foo 9 foo 10 foo 11 foo 12 foo 13 __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null 2>&1 <<\__END__ echo stdout echo stderr >&2 cat __END__ test_oe 'redirection affects editor and re-executed command' \ -i +m --rcfile="rcfile1" fc -e cat 1 3 3>&1 1>&2 2>&3 <<\_IN_ stdin _IN_ __IN__ stderr __OUT__ echo stdout echo stderr >&2 cat echo stdout echo stderr >&2 cat stdout stdin __ERR__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO cat >"$editor" <<\__END__ echo $x __END__ chmod a+x "$editor" # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null 2>&1 <<\__END__ "$editor" __END__ test_oE 'assignment affects editor and re-executed command' \ -i +m --rcfile="rcfile1" x=test_assignment fc -e "$editor" __IN__ test_assignment "$editor" test_assignment __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 __END__ # Some ed implementations seem broken in that their "w" command prints the byte # count to stderr rather than stdout. The redirection works around this in the # test case below. test_oE 'default editor is ed' -i +m --rcfile="rcfile1" fc 2 4 2>&1 <<\__ED__ 2d 1a echo bar X . w __ED__ __IN__ 33 33 echo foo 2 echo bar X echo foo 4 foo 2 bar X foo 4 __OUT__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO cat >"$editor" <<\__END__ rm -- "$1" && echo echo bar 2 >"$1" __END__ # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 __END__ chmod a+x "$editor" # In this test, the editor removes the file and creates a new file with the # same name but with a (normally) different i-node. The shell must read the new # file successfully. test_oE 're-creation of command file' -i +m --rcfile="rcfile1" fc -e "$editor" __IN__ echo bar 2 bar 2 __OUT__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO test_oe -e n 'editor returning non-zero prevents command execution' \ -i +m --rcfile="rcfile1" echo foo fc -e false __IN__ foo __OUT__ fc: the editor returned a non-zero exit status __ERR__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO test_oE '$? in executed command' -i +m --rcfile="rcfile1" true echo $? (exit 37) fc -e true 2 __IN__ 0 echo $? 37 __OUT__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO test_x -e 17 'exit status of command re-execution' -i +m --rcfile="rcfile1" (exit 17) fc -e true __IN__ ) ( export histfile=histfile$LINENO histsize=100 editor=./editor$LINENO test_oE 'interaction between shells' -i +m --rcfile="rcfile1" : foo 1 echo : bar 3 | "$TESTEE" -i +m --rcfile="rcfile1" : foo 4 fc -l __IN__ 1 : foo 1 2 echo : bar 3 | "$TESTEE" -i +m --rcfile="rcfile1" 3 : bar 3 4 : foo 4 5 fc -l __OUT__ ) test_Oe -e 2 'invalid option (non-existing option)' fc --no-such-option __IN__ fc: `--no-such-option' is not a valid option __ERR__ #` test_Oe -e 2 'invalid option (-e and -l)' fc -e xxx -l __IN__ fc: the -e option cannot be used with the -l option __ERR__ test_Oe -e 2 'invalid option (-e and -s)' fc -e xxx -s __IN__ fc: the -e option cannot be used with the -s option __ERR__ test_Oe -e 2 'invalid option (-l and -q)' fc -lq __IN__ fc: the -l option cannot be used with the -q option __ERR__ test_Oe -e 2 'invalid option (-l and -s)' fc -ls __IN__ fc: the -l option cannot be used with the -s option __ERR__ test_Oe -e 2 'invalid option (-r and -s)' fc -rs __IN__ fc: the -r option cannot be used with the -s option __ERR__ test_Oe -e 2 'invalid option (-n w/o -l)' fc -n __IN__ fc: the -n or -v option must be used with the -l option __ERR__ test_Oe -e 2 'invalid option (-v w/o -l)' fc -v __IN__ fc: the -n or -v option must be used with the -l option __ERR__ test_Oe -e 2 'too many operands (w/o -l or -s)' fc 1 2 3 __IN__ fc: too many operands are specified __ERR__ test_Oe -e 2 'too many operands (-l)' fc -l 1 2 3 __IN__ fc: too many operands are specified __ERR__ test_Oe -e 2 'too many operands (-s, w/ substitution)' fc -s 1 2 __IN__ fc: too many operands are specified __ERR__ test_Oe -e 2 'too many operands (-s, w/o substitution)' fc -s old=new 1 2 __IN__ fc: too many operands are specified __ERR__ ( export histfile=histfile$LINENO histsize=100 test_O -d -e 1 'printing to closed stream' -i +m --rcfile="rcfile1" : fc -l >&- __IN__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/fg-p.tst000066400000000000000000000022661354143602500151540ustar00rootroot00000000000000# fg-p.tst: test of the fg built-in for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% posix="true" cat >job1 <<\__END__ exec sh -c 'echo 1; kill -s STOP $$; echo 2' __END__ cat >job2 <<\__END__ exec sh -c 'echo a; kill -s STOP $$; echo b' __END__ chmod a+x job1 chmod a+x job2 mkfifo fifo test_O -d -e n 'fg cannot be used when job control is disabled' +m :& fg __IN__ test_oE 'default operand chooses most recently suspended job' -m :& sh -c 'kill -s STOP $$; echo 1' fg >/dev/null __IN__ 1 __OUT__ test_oE 'resumed job is in foreground' -m sh -c 'kill -s STOP $$; ../checkfg && echo fg' fg >/dev/null __IN__ fg __OUT__ test_x -e 127 'resumed job is disowned unless suspended again' -m cat fifo >/dev/null & exec 3>fifo kill -s STOP % exec 3>&- fg >/dev/null wait $! __IN__ test_oE 'specifying job ID' -m ./job1 ./job2 fg %./job1 >/dev/null fg %./job2 >/dev/null __IN__ 1 a 2 b __OUT__ test_oE 'fg prints resumed job' -m ./job1 fg __IN__ 1 ./job1 2 __OUT__ test_O -d -e n 'no existing job' -m fg __IN__ test_O -d -e n 'no such job' -m sh -c 'kill -s STOP $$' fg %_no_such_job_ exit_status=$? fg >/dev/null exit $exit_status __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/fg-y.tst000066400000000000000000000034171354143602500151640ustar00rootroot00000000000000# fg-y.tst: yash-specific test of the fg built-in ../checkfg || skip="true" # %REQUIRETTY% cat >job1 <<\__END__ exec sh -c 'echo 1; kill -s STOP $$; echo 2' __END__ chmod a+x job1 # POSIX requires that "fg" should return 0 on success. Yash, however, returns # the exit status of the resumed job, which is not always 0. Many other shells # behave this way. test_x -e 17 'exit status of resumed command' -m sh -c 'kill -s STOP $$; exit 17' fg __IN__ test_oE 'resuming more than one job' -m "$TESTEE" -c 'suspend; echo 1' "$TESTEE" -c 'suspend; echo 2' "$TESTEE" -c 'suspend; echo 3' fg %2 %3 '%? echo 1' >/dev/null __IN__ 2 3 1 __OUT__ test_oE 'omitting % in job ID' -m "$TESTEE" -c 'suspend; echo x' "$TESTEE" -c 'suspend; echo y' fg 2 >/dev/null fg \?x >/dev/null __IN__ y x __OUT__ test_oE 'fg prints resumed job' -m ./job1 fg __IN__ 1 [1] ./job1 2 __OUT__ test_Oe -e 1 'non-job-controlled job (default operand)' :& set -m fg __IN__ fg: the current job is not a job-controlled job __ERR__ test_Oe -e 1 'non-job-controlled job (job ID operand)' :& set -m fg %: __IN__ fg: `%:' is not a job-controlled job __ERR__ #` test_Oe -e 1 'no such job (name)' -m : _no_such_job_& fg %_no_such_job_ __IN__ fg: no such job `%_no_such_job_' __ERR__ #` test_Oe -e 1 'no such job (number)' -m fg %2 __IN__ fg: no such job `%2' __ERR__ #` test_O -d -e 1 'printing to closed stream' -m :& fg >&- __IN__ test_Oe -e 2 'invalid option' -m fg --no-such-option __IN__ fg: `--no-such-option' is not a valid option __ERR__ #` ( posix="true" test_Oe -e 2 'too many operands' -m :&:& fg %+ %- __IN__ fg: too many operands are specified __ERR__ test_Oe -e 1 'initial % cannot be omitted in POSIX mode' -m fg foo __IN__ fg: `foo' is not a valid job specification __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/fnmatch-p.tst000066400000000000000000000155641354143602500162050ustar00rootroot00000000000000# fnmatch-p.tst: test of pattern matching for any POSIX-compliant shell posix="true" test_oE 'quotations of a normal character' case a in a ) echo 01; esac case a in b ) echo 02; esac case a in A ) echo 03; esac case \a in a ) echo 11; esac case \a in b ) echo 12; esac case \a in A ) echo 13; esac case a in \a ) echo 21; esac case a in \b ) echo 22; esac case a in \A ) echo 23; esac case \a in \a ) echo 31; esac case \a in \b ) echo 32; esac case \a in \A ) echo 33; esac case 'a' in a ) echo 41; esac case 'a' in b ) echo 42; esac case 'a' in A ) echo 43; esac case a in 'a') echo 51; esac case a in 'b') echo 52; esac case a in 'A') echo 53; esac case 'a' in 'a') echo 61; esac case 'a' in 'b') echo 62; esac case 'a' in 'A') echo 63; esac case "a" in a ) echo 71; esac case "a" in b ) echo 72; esac case "a" in A ) echo 73; esac case a in "a") echo 81; esac case a in "b") echo 82; esac case a in "A") echo 83; esac case "a" in "a") echo 91; esac case "a" in "b") echo 92; esac case "a" in "A") echo 93; esac __IN__ 01 11 21 31 41 51 61 71 81 91 __OUT__ test_oE 'quotations of quotations' sq=\' dq=\" bs=\\ case \' in \' ) echo 111; esac case \' in "'" ) echo 112; esac case \' in $sq ) echo 113; esac case \' in "$sq") echo 114; esac case "'" in \' ) echo 121; esac case "'" in "'" ) echo 122; esac case "'" in $sq ) echo 123; esac case "'" in "$sq") echo 124; esac case $sq in \' ) echo 131; esac case $sq in "'" ) echo 132; esac case $sq in $sq ) echo 133; esac case $sq in "$sq") echo 134; esac case "$sq" in \' ) echo 141; esac case "$sq" in "'" ) echo 142; esac case "$sq" in $sq ) echo 143; esac case "$sq" in "$sq") echo 144; esac case \" in \" ) echo 211; esac case \" in '"' ) echo 212; esac case \" in $dq ) echo 213; esac case \" in "$dq") echo 214; esac case '"' in \" ) echo 221; esac case '"' in '"' ) echo 222; esac case '"' in $dq ) echo 223; esac case '"' in "$dq") echo 224; esac case $dq in \" ) echo 231; esac case $dq in '"' ) echo 232; esac case $dq in $dq ) echo 233; esac case $dq in "$dq") echo 234; esac case "$dq" in \" ) echo 241; esac case "$dq" in '"' ) echo 242; esac case "$dq" in $dq ) echo 243; esac case "$dq" in "$dq") echo 244; esac case \\ in \\ ) echo 311; esac case \\ in '\' ) echo 312; esac case \\ in "\\" ) echo 313; esac case \\ in $bs ) echo 314; esac case \\ in "$bs") echo 315; esac case '\' in \\ ) echo 321; esac case '\' in '\' ) echo 322; esac case '\' in "\\" ) echo 323; esac case '\' in $bs ) echo 324; esac case '\' in "$bs") echo 325; esac case "\\" in \\ ) echo 331; esac case "\\" in '\' ) echo 332; esac case "\\" in "\\" ) echo 333; esac case "\\" in $bs ) echo 334; esac case "\\" in "$bs") echo 335; esac case $bs in \\ ) echo 341; esac case $bs in '\' ) echo 342; esac case $bs in "\\" ) echo 343; esac case $bs in $bs ) echo 344; esac case $bs in "$bs") echo 345; esac case "$bs" in \\ ) echo 351; esac case "$bs" in '\' ) echo 352; esac case "$bs" in "\\" ) echo 353; esac case "$bs" in $bs ) echo 354; esac case "$bs" in "$bs") echo 355; esac case \'"'"$sq"$sq" in "$sq"$sq"'"\') echo 391; esac case \"'"'$dq"$dq" in "$dq"$dq'"'\") echo 392; esac case \\'\'"\\"$bs"$bs" in "$bs"$bs"\\"'\'\\) echo 393; esac __IN__ 111 112 113 114 121 122 123 124 131 132 133 134 141 142 143 144 211 212 213 214 221 222 223 224 231 232 233 234 241 242 243 244 311 312 313 314 315 321 322 323 324 325 331 332 333 334 335 341 342 343 344 345 351 352 353 354 355 391 392 393 __OUT__ test_oE 'blanks' n= case '' in '' ) echo 11; esac case '' in "" ) echo 12; esac case '' in $n ) echo 13; esac case '' in "$n") echo 14; esac case "" in '' ) echo 21; esac case "" in "" ) echo 22; esac case "" in $n ) echo 23; esac case "" in "$n") echo 24; esac case $n in '' ) echo 31; esac case $n in "" ) echo 32; esac case $n in $n ) echo 33; esac case $n in "$n") echo 34; esac case "$n" in '' ) echo 41; esac case "$n" in "" ) echo 42; esac case "$n" in $n ) echo 43; esac case "$n" in "$n") echo 44; esac case $n''"""$n" in "$n"""''$n) echo 99; esac __IN__ 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 99 __OUT__ test_oE '? and * and normal characters' case a in a ) echo 01; esac case aa in a ) echo 02; esac case a in aa) echo 03; esac case aa in aa) echo 04; esac case a in ? ) echo 11; esac case a in * ) echo 12; esac case a in ?*) echo 13; esac case a in *?) echo 14; esac case a in ??) echo 15; esac case a in **) echo 16; esac case aa in ? ) echo 21; esac case aa in * ) echo 22; esac case aa in ?*) echo 23; esac case aa in *?) echo 24; esac case aa in ??) echo 25; esac case aa in **) echo 26; esac __IN__ 01 04 11 12 13 14 16 22 23 24 25 26 __OUT__ test_oE '? and * and quotations' case '' in ?) echo 01; esac case '' in *) echo 02; esac case \\ in ?) echo 11; esac case \\ in *) echo 12; esac case "'" in ?) echo 21; esac case "'" in *) echo 22; esac case '"' in ?) echo 31; esac case '"' in *) echo 32; esac __IN__ 02 11 12 21 22 31 32 __OUT__ test_oE 'brackets' case a in [[:lower:]]) echo lower ; esac case a in [[:upper:]]) echo upper ; esac case a in [[:alpha:]]) echo alpha ; esac case a in [[:digit:]]) echo digit ; esac case a in [[:alnum:]]) echo alnum ; esac case a in [[:punct:]]) echo punct ; esac case a in [[:graph:]]) echo graph ; esac case a in [[:print:]]) echo print ; esac case a in [[:cntrl:]]) echo cntrl ; esac case a in [[:blank:]]) echo blank ; esac case a in [[:space:]]) echo space ; esac case a in [[:xdigit:]]) echo xdigit; esac case a in [[.a.]] ) echo 2; esac case 1 in [0-2] ) echo 3; esac case 1 in [[.0.]-[.2.]]) echo 4; esac case a in [!a] ) echo 5; esac case 1 in [!0-2] ) echo 6; esac case a in [[=a=]] ) echo 7; esac __IN__ lower alpha alnum graph print xdigit 2 3 4 7 __OUT__ test_oE 'brackets and quotations' case \. in ["."]) echo 01; esac case \[ in ["."]) echo 02; esac case \" in ["."]) echo 03; esac case \\ in ["."]) echo 04; esac case \] in ["."]) echo 05; esac case \. in [\".]) echo 11; esac case \[ in [\".]) echo 12; esac case \" in [\".]) echo 13; esac case \\ in [\".]) echo 14; esac case \] in [\".]) echo 15; esac case \. in [\.] ) echo 21; esac case \[ in [\.] ) echo 22; esac case \" in [\.] ) echo 23; esac case \\ in [\.] ) echo 24; esac case \] in [\.] ) echo 25; esac case \. in "[.]") echo 31; esac case \[ in "[.]") echo 32; esac case \" in "[.]") echo 33; esac case \\ in "[.]") echo 34; esac case \] in "[.]") echo 35; esac case \. in [\]] ) echo 41; esac case \[ in [\]] ) echo 42; esac case \" in [\]] ) echo 43; esac case \\ in [\]] ) echo 44; esac case \] in [\]] ) echo 45; esac case \. in ["]"]) echo 51; esac case \[ in ["]"]) echo 52; esac case \" in ["]"]) echo 53; esac case \\ in ["]"]) echo 54; esac case \] in ["]"]) echo 55; esac __IN__ 01 11 13 21 45 55 __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/for-p.tst000066400000000000000000000043521354143602500153440ustar00rootroot00000000000000# for-p.tst: test of for loop for any POSIX-compliant shell posix="true" test_OE 'default words, no positional parameters' for i do echo not reached done __IN__ test_oE 'default words, one positional parameter' -s A for i do echo $i done __IN__ A __OUT__ test_oE 'default words, one positional parameter' -s A ' B B ' for word do echo "$word" done __IN__ A B B __OUT__ test_OE 'explicit words, no words, newline-separated do' for i in do echo not reached done __IN__ test_oE 'explicit words, one word, newline-separated do' for i in do do echo $i done __IN__ do __OUT__ test_oE 'explicit words, two words, newline-separated do' for word in do done do echo $word done __IN__ do done __OUT__ test_oE 'expansion of words' HOME=/home for i in ~ $HOME $(echo foo) $((1+2)) do echo $i done for i in $(echo foo bar) do echo $i done for i in do echo $i done __IN__ /home /home foo 3 foo bar __OUT__ test_oE 'words are not treated as assignments' v=foo for i in v=bar; do echo $i $v; done __IN__ v=bar foo __OUT__ test_oE 'semicolon-separated commands' for v in 1 2; do echo $v; done __IN__ 1 2 __OUT__ test_oE 'commands ending with an asynchronous command' for v in 1 2; do true; echo& done wait __IN__ __OUT__ test_oE 'for as variable name' -s foo for for do echo $for; done __IN__ foo __OUT__ test_oE 'do as variable name' -s foo for do do echo $do; done __IN__ foo __OUT__ test_oE 'in as variable name' for in in foo; do echo $in; done __IN__ foo __OUT__ test_oE 'in as word' for i in in; do echo $i; done __IN__ in __OUT__ test_oE -e 0 'default words, do separated by semicolon' -s A B for i; do echo $i; done __IN__ A B __OUT__ test_oE -e 0 'default words, do separated by semicolon and newlines' -s A B for i ; do echo $i; done __IN__ A B __OUT__ test_x -e 0 'exit status with no words' false for i do false done __IN__ test_x -e 3 'exit status with some words' for x in 1 2 3 do (exit $x) done __IN__ test_o 'redirection on for loop' for i in a b c; do read j; echo $i $j; done >redir_out </dev/null </dev/null __IN__ syntax error: a function body must be a compound command __ERR__ #' #` test_Oe -e 2 'simple command as function body (w/ function keyword)' function foo() echo >/dev/null __IN__ syntax error: a function body must be a compound command __ERR__ #' #` test_Oe -e 2 'function definition as function body' foo() bar() { :; } __IN__ syntax error: a function body must be a compound command __ERR__ test_Oe -e 2 'function followed by EOF' function __IN__ syntax error: a word is required after `function' syntax error: a function body must be a compound command __ERR__ #' #` test_Oe -e 2 'function followed by symbol' function | __IN__ syntax error: a word is required after `function' syntax error: a function body must be a compound command __ERR__ #' #` test_Oe -e 2 'function followed by newline' function ### foo() { :; } __IN__ syntax error: a word is required after `function' syntax error: a function body must be a compound command __ERR__ #' #` test_oE 'function as function name' function function () { echo foo } \function __IN__ foo __OUT__ test_Oe -e 2 'function name followed by EOF (w/ function keyword)' function foo __IN__ syntax error: a function body must be a compound command __ERR__ test_Oe -e 2 'parentheses followed by EOF (w/ function keyword)' function foo() __IN__ syntax error: a function body must be a compound command __ERR__ test_Oe -e 2 'parentheses followed by EOF (w/o function keyword)' foo() __IN__ syntax error: a function body must be a compound command __ERR__ test_Oe -e 2 'unpaired parenthesis (w/o function keyword)' foo( __IN__ syntax error: `(' must be followed by `)' in a function definition __ERR__ #' #` #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/getopts-p.tst000066400000000000000000000113541354143602500162430ustar00rootroot00000000000000# getopts-p.tst: test of the getopts built-in for any POSIX-compliant shell posix="true" test_o 'default OPTIND is 1' printf '%s\n' "$OPTIND" __IN__ 1 __OUT__ test_o 'OPTIND and OPTARG are not exported by default' getopts a: o -a arg getopts a: o -a arg sh -c 'echo ${OPTIND-unset} ${OPTARG-unset}' __IN__ 1 unset __OUT__ test_o 'operand variable is updated to parsed option on each invocation' getopts ab:c o -a -b arg -c printf '1[%s]\n' "$o" getopts ab:c o -a -b arg -c printf '2[%s]\n' "$o" getopts ab:c o -a -b arg -c printf '3[%s]\n' "$o" __IN__ 1[a] 2[b] 3[c] __OUT__ test_x -e 0 'exit status is zero after option is parsed' -e getopts ab:c o -a -b arg -c getopts ab:c o -a -b arg -c getopts ab:c o -a -b arg -c __IN__ test_x -e n 'exit status is non-zero after parsing all options' -e getopts ab:c o -a -b arg -c getopts ab:c o -a -b arg -c getopts ab:c o -a -b arg -c getopts ab:c o -a -b arg -c __IN__ test_o 'OPTARG is set when option argument is parsed: empty' getopts a: o -a '' echo "[$OPTARG]" __IN__ [] __OUT__ test_o 'OPTARG is set when option argument is parsed: non-empty separate' getopts a: o -a '-x foo' echo "[$OPTARG]" __IN__ [-x foo] __OUT__ test_o 'OPTARG is set when option argument is parsed: non-empty adjoined' getopts a: o -a' foo' echo "[$OPTARG]" __IN__ [ foo] __OUT__ test_o 'OPTARG is unset when option without argument is parsed' getopts a o -a echo "${OPTARG-un}${OPTARG-set}" __IN__ unset __OUT__ test_o 'operand variable is set to "?" on unknown option' getopts '' o -a printf '[%s]\n' "$o" __IN__ [?] __OUT__ test_o 'OPTARG is set to the option on unknown option (with :)' getopts : o -a printf '[%s]\n' "$OPTARG" __IN__ [a] __OUT__ test_E 'no error message on unknown option (with :)' getopts : o -a __IN__ test_o 'OPTARG is unset on unknown option (without :)' getopts '' o -a printf '%s\n' "${OPTARG-un}${OPTARG-set}" __IN__ unset __OUT__ test_x -d 'error message is printed on unknown option (without :)' getopts '' o -a __IN__ test_o 'operand variable is set to ":" on missing option argument (with :)' getopts :a: v -a printf '[%s]\n' "$v" __IN__ [:] __OUT__ test_o 'OPTARG is set to the option on missing option argument (with :)' getopts :a: v -a printf '[%s]\n' "$OPTARG" __IN__ [a] __OUT__ test_o 'operand variable is set to "?" on missing option argument (without :)' getopts a: v -a printf '[%s]\n' "$v" __IN__ [?] __OUT__ test_o 'OPTARG is unset on missing option argument (without :)' getopts a: v -a printf '%s\n' "${OPTARG-un}${OPTARG-set}" __IN__ unset __OUT__ test_x -d 'error message is printed on missing option argument (without :)' getopts a: v -a __IN__ test_o 'operand variable is set to "?" after parsing all options' getopts a x -a getopts a x -a printf '[%s]\n' "$x" __IN__ [?] __OUT__ test_o 'OPTARG is unset after parsing all options' getopts a x -a getopts a x -a printf '%s\n' "${OPTARG-un}${OPTARG-set}" __IN__ unset __OUT__ test_o 'options can be grouped after single hyphen' getopts abc o -abc printf '1[%s]\n' "$o" getopts abc o -abc printf '2[%s]\n' "$o" getopts abc o -abc printf '3[%s]\n' "$o" getopts abc o -abc printf '4[%s]\n' "$o" __IN__ 1[a] 2[b] 3[c] 4[?] __OUT__ test_x -e n 'single hyphen is not option' getopts '' x - __IN__ test_o 'double hyphen separates options and operands' getopts ab x -a -- -b printf '1[%s]\n' "$x" getopts ab x -a -- -b || printf '2[%d]\n' "$OPTIND" __IN__ 1[a] 2[3] __OUT__ test_o 'OPTIND is first operand index after parsing all options: no operand, no --' getopts '' x printf '%d\n' "$OPTIND" __IN__ 1 __OUT__ test_o 'OPTIND is first operand index after parsing all options: one operand, no --' getopts '' x operand printf '%d\n' "$OPTIND" __IN__ 1 __OUT__ test_o 'OPTIND is first operand index after parsing all options: no operand, with --' getopts '' x -- printf '%d\n' "$OPTIND" __IN__ 2 __OUT__ test_o 'OPTIND is first operand index after parsing all options: one operand, with --' getopts '' x -- operand printf '%d\n' "$OPTIND" __IN__ 2 __OUT__ test_o 'resetting OPTIND to parse another arguments' getopts ab p -a -b getopts ab p -a -b getopts ab p -a -b OPTIND=1 getopts xy q -x -y printf '1[%s]\n' "$q" getopts xy q -x -y printf '2[%s]\n' "$q" getopts xy q -x -y printf '3[%d]\n' "$OPTIND" __IN__ 1[x] 2[y] 3[3] __OUT__ test_o 'positional parameters are parsed by default' -s -- -a -b arg -c getopts ab:c o printf '1[%s]\n' "$o" getopts ab:c o printf '2[%s]\n' "$o" getopts ab:c o printf '3[%s]\n' "$o" __IN__ 1[a] 2[b] 3[c] __OUT__ test_o 'option characters are alphanumeric' getopts ab:01: o -a -b arg -1 -2 -0 printf '1[%s]\n' "$o" getopts ab:01: o -a -b arg -1 -2 -0 printf '2[%s]\n' "$o" getopts ab:01: o -a -b arg -1 -2 -0 printf '3[%s]\n' "$o" getopts ab:01: o -a -b arg -1 -2 -0 printf '4[%s]\n' "$o" __IN__ 1[a] 2[b] 3[1] 4[0] __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/getopts-y.tst000066400000000000000000000045051354143602500162540ustar00rootroot00000000000000# getopts-y.tst: yash-specific test of the getopts built-in test_E 'no error message on missing option argument (with :)' getopts :a: o -a __IN__ test_o '":" is not parsed as valid option' getopts : o -: echo "$?" "$o" "$OPTARG" __IN__ 0 ? : __OUT__ ( posix="true" test_Oe -e 1 'invalid option candidate "?"' getopts '?' o __IN__ getopts: `?' is not a valid option specification __ERR__ #' #` test_Oe -e 1 'invalid option candidate ":"' getopts :: o __IN__ getopts: `::' is not a valid option specification __ERR__ #' #` test_Oe -e 1 'invalid option candidate "-"' getopts - o __IN__ getopts: `-' is not a valid option specification __ERR__ #' #` test_Oe -e 1 'invalid option candidate "+"' getopts + o __IN__ getopts: `+' is not a valid option specification __ERR__ #' #` ) test_Oe -e 1 'invalid operand variable name' getopts '' = __IN__ getopts: `=' is not a valid variable name __ERR__ #' #` test_Oe -e 1 'unset OPTIND' unset OPTIND getopts a o -a __IN__ getopts: $OPTIND has an invalid value __ERR__ test_Oe -e 1 'empty OPTIND' OPTIND= getopts a o -a __IN__ getopts: $OPTIND has an invalid value __ERR__ test_Oe -e 1 'non-numeric OPTIND' OPTIND=X getopts a o -a __IN__ getopts: $OPTIND has an invalid value __ERR__ test_oE 'OPTIND argument index out-of-range' OPTIND=100 getopts a o -a echo "$?" "$o" "$OPTIND" __IN__ 1 ? 100 __OUT__ test_oE 'OPTIND option index out-of-range' OPTIND=1:10 getopts abc o -abc echo "$?" "$o" "$OPTIND" __IN__ 1 ? 2 __OUT__ test_oE 'getopts has no effect after all options have been parsed' getopts a o -a getopts a o -a echo "$?" "$o" "$OPTIND" getopts a o -a echo "$?" "$o" "$OPTIND" __IN__ 1 ? 2 1 ? 2 __OUT__ test_Oe -e 1 'read-only operand variable' readonly o getopts a o -a __IN__ getopts: $o is read-only __ERR__ test_Oe -e 1 'read-only OPTARG' readonly OPTARG getopts a: o -a foo __IN__ getopts: $OPTARG is read-only __ERR__ test_Oe -e 1 'read-only OPTIND' readonly OPTIND getopts a o -a __IN__ getopts: $OPTIND is read-only __ERR__ test_Oe -e 2 'invalid option' getopts --no-such-option a o -a __IN__ getopts: `--no-such-option' is not a valid option __ERR__ #' #` test_Oe -e 2 'missing operand (0)' getopts __IN__ getopts: this command requires 2 operands __ERR__ test_Oe -e 2 'missing operand (1)' getopts a __IN__ getopts: this command requires 2 operands __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/grouping-p.tst000066400000000000000000000023321354143602500164040ustar00rootroot00000000000000# grouping-p.tst: test of grouping commands for any POSIX-compliant shell posix="true" mkfifo fifo1 test_oE 'effect of subshell' a=1 (a=2; echo $a; exit; echo not reached) echo $a __IN__ 2 1 __OUT__ test_x -e 23 'exit status of subshell' (true; exit 23) __IN__ test_oE 'redirection on subshell' (echo 1; echo 2; echo 3; echo 4) >sub_out (tail -n 2) fifo1&) cat fifo1 __IN__ foo __OUT__ test_oE 'newlines in subshell' ( echo foo ) __IN__ foo __OUT__ test_oE 'effect of brace grouping' a=1 { a=2; echo $a; exit; echo not reached; } echo not reached __IN__ 2 __OUT__ test_x -e 29 'exit status of brace grouping' { true; sh -c 'exit 29'; } __IN__ test_oE 'redirection on brace grouping' { echo 1; echo 2; echo 3; echo 4; } >brace_out { tail -n 2; } fifo1&} cat fifo1 __IN__ foo __OUT__ test_oE 'newlines in brace grouping' { echo foo } __IN__ foo __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/grouping-y.tst000066400000000000000000000040111354143602500164110ustar00rootroot00000000000000# grouping-y.tst: yash-specific test of grouping commands test_oE 'effect of empty subshell' echo 1 () echo 2 __IN__ 1 2 __OUT__ test_OE -e 11 'exit status of empty subshell' sh -c 'exit 11' () __IN__ test_oE 'effect of empty brace grouping' echo 1 { } echo 2 __IN__ 1 2 __OUT__ test_OE -e 13 'exit status of empty brace grouping' sh -c 'exit 13' { } __IN__ ( posix="true" test_OE -e 0 '} after )' # In a literal interpretation of POSIX XCU 2.4, this should be a syntax error # because } does not follow any reserved word. However, no known shell rejects # this. { (:) } __IN__ test_OE -e 0 ') after }' # This is of course OK. ( { :; } ) __IN__ test_Oe -e 2 'empty subshell (single line)' () __IN__ syntax error: commands are missing between `(' and `)' __ERR__ #'`'` test_Oe -e 2 'empty subshell (multi-line)' ( ) __IN__ syntax error: commands are missing between `(' and `)' __ERR__ #'`'` test_Oe -e 2 'empty brace grouping (single line)' { } __IN__ syntax error: commands are missing between `{' and `}' __ERR__ #'`'` test_Oe -e 2 'empty brace grouping (multi-line)' { } __IN__ syntax error: commands are missing between `{' and `}' __ERR__ #'`'` ) test_Oe -e 2 'unpaired )' ) __IN__ syntax error: encountered `)' without a matching `(' __ERR__ #'`'` test_Oe -e 2 'unpaired }' } __IN__ syntax error: encountered `}' without a matching `{' __ERR__ #'`'` test_Oe -e 2 'unclosed subshell' ( echo foo __IN__ syntax error: `)' is missing __ERR__ #'` test_Oe -e 2 'unclosed brace grouping' { echo foo __IN__ syntax error: `}' is missing __ERR__ #'` test_Oe -e 2 'unclosed subshell in brace grouping' { ( } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `)'?) __ERR__ #'`'`'` test_Oe -e 2 'unclosed brace grouping in subshell' ( { ) __IN__ syntax error: encountered `)' without a matching `(' syntax error: (maybe you missed `}'?) __ERR__ #'`'`'` test_Oe -e 2 'simple command followed by (' echo foo ( :) __IN__ syntax error: invalid use of `(' __ERR__ #'` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/hash-y.tst000066400000000000000000000103321354143602500155050ustar00rootroot00000000000000# hash-y.tst: yash-specific test of the hash built-in # Tests for the -d option are omitted because we cannot test it portably. # Prevent the echo command from being hashed for consistent results. setup 'echo() (command echo "$@")' ( # Ensure $PWD is safe to assign to $PATH case $PWD in (*[:%]*) skip="true" esac setup - <<\__END__ mkdir "$TEST_NO.path" && cd "$TEST_NO.path" make_command() for c do echo echo "Running $c" >"$c" && chmod a+x "$c"; done __END__ export TEST_NO="$LINENO" test_oE 'remembering command path (by hash built-in)' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command b/command1 c/command1 hash command1 echo --- $? make_command a/command1 command1 __IN__ --- 0 Running b/command1 __OUT__ export TEST_NO="$LINENO" test_oE 'remembering command path (by executing command)' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command b/command1 c/command1 command1 echo --- make_command a/command1 command1 __IN__ Running b/command1 --- Running b/command1 __OUT__ export TEST_NO="$LINENO" test_oE 're-remembering command path' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command b/command1 c/command1 hash command1 echo --- rm b/command1 hash command1 echo --- $? make_command a/command1 command1 __IN__ --- --- 0 Running c/command1 __OUT__ export TEST_NO="$LINENO" test_oE 'removing specific remembered command path' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command c/command1 c/command2 hash command1 command2 echo --- $? make_command b/command1 b/command2 hash -r command1 make_command a/command1 a/command2 command1 command2 __IN__ --- 0 Running a/command1 Running c/command2 __OUT__ export TEST_NO="$LINENO" test_oE 'removing all remembered command paths' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command c/command1 c/command2 hash command1 command2 make_command b/command1 b/command2 hash -r echo --- $? make_command a/command1 a/command2 command1 command2 __IN__ Running c/command2 --- 0 Running a/command1 Running a/command2 __OUT__ export TEST_NO="$LINENO" test_oE 'remembering multiple command paths' mkdir a b PATH=$PWD/a:$PWD/b:$PATH make_command b/command1 b/command2 hash command1 command2 echo --- $? make_command a/command1 a/command2 command1 command2 __IN__ --- 0 Running b/command1 Running b/command2 __OUT__ export TEST_NO="$LINENO" test_oE 'removing multiple remembered command paths' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command c/command1 c/command2 hash command1 command2 make_command b/command1 b/command2 hash -r command1 command2 echo --- $? make_command a/command1 a/command2 command1 command2 __IN__ --- 0 Running a/command1 Running a/command2 __OUT__ export TEST_NO="$LINENO" testcase "$LINENO" 'printing remembered commands (with -a)' \ 3<<\__IN__ 4<<__OUT__ 5&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/help-y.tst000066400000000000000000000367201354143602500155230ustar00rootroot00000000000000# help-y.tst: yash-specific test of the help built-in if ! testee --version --verbose | grep -Fqx ' * help'; then skip="true" fi test_oE -e 0 'help is a semi-special built-in' command -V help __IN__ help: a semi-special built-in __OUT__ test_oE -e 0 'without arguments, the help for the help itself is printed' help __IN__ help: print usage of built-in commands Syntax: help [built-in...] Options: --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of alias' help alias __IN__ alias: define or print aliases Syntax: alias [-gp] [name[=value]...] Options: -g --global -p --prefix --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv array' >/dev/null; then skip="true" fi test_oE -e 0 'help of array' help array __IN__ array: manipulate an array Syntax: array # print arrays array name [value...] # set array values array -d name [index...] array -i name index [value...] array -s name index value Options: -d --delete -i --insert -s --set --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of bg' help bg __IN__ bg: run jobs in the background Syntax: bg [job...] Options: --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv bindkey' >/dev/null; then skip="true" fi test_oE -e 0 'help of bindkey' help bindkey __IN__ bindkey: set or print key bindings for line-editing Syntax: bindkey -aev [key_sequence [command]] bindkey -l Options: -v --vi-insert -a --vi-command -e --emacs -l --list --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of break' help break __IN__ break: exit a loop Syntax: break [count] break -i Options: -i --iteration --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of cd' help cd __IN__ cd: change the working directory Syntax: cd [-L|-P] [directory] Options: -d ... --default-directory=... -L --logical -P --physical --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of colon' help : __IN__ :: do nothing Syntax: : [...] Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of command' help command __IN__ command: execute or identify a command Syntax: command [-befp] command [argument...] command -v|-V [-abefkp] command... Options: -a --alias -b --builtin-command -e --external-command -f --function -k --keyword -p --standard-path -v --identify -V --verbose-identify --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv complete' >/dev/null; then skip="true" fi test_oE -e 0 'help of complete' help complete __IN__ complete: generate completion candidates Syntax: complete [-A pattern] [-R pattern] [-T] [-P prefix] [-S suffix] \ [-abcdfghjkuv] [[-O] [-D description] words...] Options: -A ... --accept=... -a --alias --array-variable --bindkey -b --builtin-command -c --command -D ... --description=... -d --directory --dirstack-index --executable-file --external-command -f --file --finished-job --function --global-alias -g --group --help -h --hostname -j --job -k --keyword -T --no-termination --normal-alias -O --option -P ... --prefix=... --regular-builtin -R ... --reject=... --running-job --scalar-variable --semi-special-builtin --signal --special-builtin --stopped-job -S ... --suffix=... -u --username -v --variable Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of continue' help continue __IN__ continue: continue a loop Syntax: continue [count] continue -i Options: -i --iteration --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv dirs' >/dev/null; then skip="true" fi test_oE -e 0 'help of dirs' help dirs __IN__ dirs: print the directory stack Syntax: dirs [-cv] [index...] Options: -c --clear -v --verbose --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of disown' help disown __IN__ disown: disown jobs Syntax: disown [job...] disown -a Options: -a --all --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of dot' help . __IN__ .: read a file and execute commands Syntax: . [-AL] file [argument...] Options: -A --no-alias -L --autoload --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv echo' >/dev/null; then skip="true" fi test_oE -e 0 'help of echo' help echo __IN__ echo: print arguments Syntax: echo [string...] Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of eval' help eval __IN__ eval: evaluate arguments as a command Syntax: eval [-i] [argument...] Options: -i --iteration --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of exec' help exec __IN__ exec: replace the shell process with an external command Syntax: exec [-cf] [-a name] [command [argument...]] Options: -a ... --as=... -c --clear -f --force --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of exit' help exit __IN__ exit: exit the shell Syntax: exit [-f] [exit_status] Options: -f --force --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of export' help export __IN__ export: export variables as environment variables Syntax: export [-prX] [name[=value]...] Options: -f --functions -g --global -p --print -r --readonly -x --export -X --unexport --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of false' help false __IN__ false: do nothing unsuccessfully Syntax: false Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv fc' >/dev/null; then skip="true" fi test_oE -e 0 'help of fc' help fc __IN__ fc: list or re-execute command history Syntax: fc [-qr] [-e editor] [first [last]] fc -s [-q] [old=new] [first] fc -l [-nrv] [first [last]] Options: -e ... --editor=... -l --list -n --no-numbers -q --quiet -r --reverse -s --silent -v --verbose --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of fg' help fg __IN__ fg: run jobs in the foreground Syntax: fg [job...] Options: --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of getopts' help getopts __IN__ getopts: parse command options Syntax: getopts options variable [argument...] Options: --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of hash' help hash __IN__ hash: remember, forget, or report command locations Syntax: hash command... hash -r [command...] hash [-a] # print remembered paths hash -d user... hash -d -r [user...] hash -d # print remembered paths Options: -a --all -d --directory -r --remove --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of help' help help __IN__ help: print usage of built-in commands Syntax: help [built-in...] Options: --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv history' >/dev/null; then skip="true" fi test_oE -e 0 'help of history' help history __IN__ history: manage command history Syntax: history [-cF] [-d entry] [-s command] [-r file] [-w file] [count] Options: -c --clear -d ... --delete=... -r ... --read=... -s ... --set=... -w ... --write=... -F --flush-file --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of jobs' help jobs __IN__ jobs: print info about jobs Syntax: jobs [-lnprs] [job...] Options: -l --verbose -n --new -p --pgid-only -r --running-only -s --stopped-only --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of kill' help kill __IN__ kill: send a signal to processes Syntax: kill [-signal|-s signal|-n number] process... kill -l [-v] [number...] Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of local' help local __IN__ local: set or print local variables Syntax: local [-prxX] [name[=value]...] Options: -p --print -r --readonly -x --export -X --unexport --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv popd' >/dev/null; then skip="true" fi test_oE -e 0 'help of popd' help popd __IN__ popd: pop a directory from the directory stack Syntax: popd [index] Options: --help Try `man yash' for details. __OUT__ #` ) ( if ! testee -c 'command -bv printf' >/dev/null; then skip="true" fi test_oE -e 0 'help of printf' help printf __IN__ printf: print a formatted string Syntax: printf format [value...] Options: --help Try `man yash' for details. __OUT__ #` ) ( if ! testee -c 'command -bv pushd' >/dev/null; then skip="true" fi test_oE -e 0 'help of pushd' help pushd __IN__ pushd: push a directory into the directory stack Syntax: pushd [-L|-P] [directory] Options: -D --remove-duplicates -d ... --default-directory=... -L --logical -P --physical --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of pwd' help pwd __IN__ pwd: print the working directory Syntax: pwd [-L|-P] Options: -L --logical -P --physical --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of read' help read __IN__ read: read a line from the standard input Syntax: read [-Aer] [-P|-p] variable... Options: -A --array -e --line-editing -P --ps1 -p ... --prompt=... -r --raw-mode --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of readonly' help readonly __IN__ readonly: make variables read-only Syntax: readonly [-fpxX] [name[=value]...] Options: -f --functions -g --global -p --print -r --readonly -x --export -X --unexport --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of return' help return __IN__ return: return from a function or script Syntax: return [-n] [exit_status] Options: -n --no-return --help Try `man yash' for details. __OUT__ #` ( if ! testee --version --verbose | grep -Fqx ' * lineedit'; then skip="true" fi test_oE -e 0 'help of set' help set __IN__ set: set shell options and positional parameters Syntax: set [option...] [--] [new_positional_parameter...] set -o|+o # print current settings Options: -a -o allexport -o braceexpand -o caseglob +C -o clobber -c -o cmdline -o curasync -o curbg -o curstop -o dotglob -o emacs -o emptylastfield -e -o errexit -o errreturn +n -o exec -o extendedglob -o forlocal +f -o glob -h -o hashondef -o histspace -o ignoreeof -i -o interactive -o lealwaysrp -o lecompdebug -o leconvmeta -o lenoconvmeta -o lepredict -o lepredictempty -o lepromptsp -o levisiblebell -o log -l -o login -o markdirs -m -o monitor -b -o notify -o notifyle -o nullglob -o pipefail -o posixlycorrect -s -o stdin -o traceall +u -o unset -v -o verbose -o vi -x -o xtrace Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of shift' help shift __IN__ shift: remove some positional parameters or array elements Syntax: shift [-A array_name] [count] Options: -A ... --array=... --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of suspend' help suspend __IN__ suspend: suspend the shell Syntax: suspend [-f] Options: -f --force --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv test' >/dev/null; then skip="true" fi test_oE -e 0 'help of test' help test __IN__ test: evaluate a conditional expression Syntax: test expression [ expression ] Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of [' help [ __IN__ [: evaluate a conditional expression Syntax: test expression [ expression ] Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of times' help times __IN__ times: print CPU time usage Syntax: times Options: --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of trap' help trap __IN__ trap: set or print signal handlers Syntax: trap [action signal...] trap signal_number [signal...] trap -p [signal...] Options: -p --print --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of true' help true __IN__ true: do nothing successfully Syntax: true Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of type' help type __IN__ type: identify a command Syntax: type command... Options: -a --alias -b --builtin-command -e --external-command -f --function -k --keyword -p --standard-path -v --identify -V --verbose-identify --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of typeset' help typeset __IN__ typeset: set or print variables Syntax: typeset [-fgprxX] [name[=value]...] Options: -f --functions -g --global -p --print -r --readonly -x --export -X --unexport --help Try `man yash' for details. __OUT__ #` ( if ! testee -c 'command -bv ulimit' >/dev/null; then skip="true" fi test_x -e 0 'help of ulimit: exit status' help ulimit __IN__ test_oE 'help of ulimit: output' help ulimit | grep -v '^ -[eilmqruvx]' __IN__ ulimit: set or print a resource limitation Syntax: ulimit -a [-H|-S] ulimit [-H|-S] [-efilnqrstuvx] [limit] Options: -H --hard -S --soft -a --all -c --core -d --data -f --fsize -n --nofile -s --stack -t --cpu --help Try `man yash' for details. __OUT__ #` ) test_oE -e 0 'help of umask' help umask __IN__ umask: print or set the file creation mask Syntax: umask mode umask [-S] Options: -S --symbolic --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of unalias' help unalias __IN__ unalias: undefine aliases Syntax: unalias name... unalias -a Options: -a --all --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of unset' help unset __IN__ unset: remove variables or functions Syntax: unset [-fv] [name...] Options: -f --functions -v --variables --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'help of wait' help wait __IN__ wait: wait for jobs to terminate Syntax: wait [job or process_id...] Options: --help Try `man yash' for details. __OUT__ #` test_oE -e 0 'specifying many operands' help true false help __IN__ true: do nothing successfully Syntax: true false: do nothing unsuccessfully Syntax: false help: print usage of built-in commands Syntax: help [built-in...] Options: --help Try `man yash' for details. __OUT__ #` test_Oe -e n 'invalid option' help --no-such-option __IN__ help: `--no-such-option' is not a valid option __ERR__ #` test_Oe -e n 'invalid operand' help XXX __IN__ help: no such built-in `XXX' __ERR__ #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/history-y.tst000066400000000000000000000177461354143602500163030ustar00rootroot00000000000000# history-y.tst: yash-specific test of the history built-in if ! testee -c 'command -bv history' >/dev/null; then skip="true" fi test_oE -e 0 'history is a semi-special built-in' command -V history __IN__ history: a semi-special built-in __OUT__ cat >rcfile1 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile HISTSIZE=$histsize unset HISTRMDUP __END__ ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 echo foo 11 echo foo 12 echo foo 13 echo foo 14 echo foo 15 echo foo 16 echo foo 17 echo foo 18 echo foo 19 echo foo 20 __END__ test_oE -e 0 'whole history is printed by default' -i +m --rcfile="rcfile1" history __IN__ 1 echo foo 1 2 echo foo 2 3 echo foo 3 4 echo foo 4 5 echo foo 5 6 echo foo 6 7 echo foo 7 8 echo foo 8 9 echo foo 9 10 echo foo 10 11 echo foo 11 12 echo foo 12 13 echo foo 13 14 echo foo 14 15 echo foo 15 16 echo foo 16 17 echo foo 17 18 echo foo 18 19 echo foo 19 20 echo foo 20 21 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 echo foo 7 echo foo 8 echo foo 9 echo foo 10 __END__ test_oE -e 0 'printing specified number of entries' -i +m --rcfile="rcfile1" history 7 __IN__ 5 echo foo 5 6 echo foo 6 7 echo foo 7 8 echo foo 8 9 echo foo 9 10 echo foo 10 11 history 7 __OUT__ test_OE -e 0 'clearing history (-c)' -i +m --rcfile="rcfile1" : history -c; history __IN__ test_OE -e 0 'clearing history (--clear)' -i +m --rcfile="rcfile1" : history --clear; history __IN__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 __END__ test_oE -e 0 'deleting entry (-d)' -i +m --rcfile="rcfile1" history -d 3; history __IN__ 1 echo foo 1 2 echo foo 2 4 echo foo 4 5 history -d 3; history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 echo foo 6 __END__ test_oE -e 0 'deleting entries (-d, --delete)' -i +m --rcfile="rcfile1" history -d 2 --delete=echo; history __IN__ 1 echo foo 1 3 echo foo 3 4 echo foo 4 5 echo foo 5 7 history -d 2 --delete=echo; history __OUT__ test_Oe -e 1 'deleting non-existing entry (-d)' -i +m --rcfile="rcfile1" history -d XXX __IN__ history: no such history entry beginning with `XXX' __ERR__ #` # Essential part of the test of the -F option is missing because we cannot test # it. test_OE -e 0 'refreshing history file (-F)' -i +m --rcfile="rcfile1" history -F __IN__ test_OE -e 0 'refreshing history file (--flush-file)' -i +m --rcfile="rcfile1" history --flush-file __IN__ ) ( export in=./in$LINENO cat >"$in" <<\__END__ echo bar 1\ echo bar 2 __END__ ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE -e 0 'reading entries (-r)' -i +m --rcfile="rcfile1" history -r "$in" history __IN__ 1 echo foo 1 2 echo foo 2 3 history -r "$in" 4 echo bar 1\ 5 echo bar 2 6 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE -e 0 'reading entries (--read)' -i +m --rcfile="rcfile1" history --read="$in" history __IN__ 1 echo foo 1 2 echo foo 2 3 history --read="$in" 4 echo bar 1\ 5 echo bar 2 6 history __OUT__ ) test_O -d -e 1 'reading commands from non-existing file' history -r _no_such_file_ __IN__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE 'replacing entry (-s)' -i +m --rcfile="rcfile1" history -s 'echo bar X' echo [$?] history __IN__ [0] 1 echo foo 1 2 echo foo 2 3 echo bar X 4 echo [$?] 5 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE 'replacing entry (--set)' -i +m --rcfile="rcfile1" history --set='echo bar X' echo [$?] history __IN__ [0] 1 echo foo 1 2 echo foo 2 3 echo bar X 4 echo [$?] 5 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE 'replacing and adding entries (-s, --set)' -i +m --rcfile="rcfile1" history -s 'echo bar X' --set='echo bar Y' -s 'echo bar Z' echo [$?] history __IN__ [0] 1 echo foo 1 2 echo foo 2 3 echo bar X 4 echo bar Y 5 echo bar Z 6 echo [$?] 7 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 test_oE 'adding entry to empty history (-s)' -i +m --rcfile="rcfile1" history -c; history -s 'echo foo 1' echo [$?] history __IN__ [0] 1 echo foo 1 2 echo [$?] 3 history __OUT__ ) ( export histfile=histfile$LINENO histsize=100 out=./out$LINENO # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE 'writing entries to new file (-w)' -i +m --rcfile="rcfile1" history -w "$out" echo [$?] cat "$out" __IN__ [0] echo foo 1 echo foo 2 history -w "$out" __OUT__ ) ( export histfile=histfile$LINENO histsize=100 out=./out$LINENO # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ test_oE 'writing entries to new file (--write)' -i +m --rcfile="rcfile1" history --write="$out" echo [$?] cat "$out" __IN__ [0] echo foo 1 echo foo 2 history --write="$out" __OUT__ ) ( export histfile=histfile$LINENO histsize=100 out=./out$LINENO # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ cat >"$out" <<\__END__ This file is overwritten. __END__ test_oE 'writing entries to existing file (-w)' -i +m --rcfile="rcfile1" history -w "$out" echo [$?] cat "$out" __IN__ [0] echo foo 1 echo foo 2 history -w "$out" __OUT__ ) ( export histfile=histfile$LINENO histsize=100 out=./out$LINENO # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 __END__ >"$out" chmod a-w "$out" # Skip if we're root. if { echo >>"$out"; } 2>/dev/null; then skip="true" fi test_O -d -e 1 'writing entries to protected file (-w)' -i +m --rcfile="rcfile1" history -w "$out" __IN__ ) ( export histfile=histfile$LINENO histsize=100 in=./in$LINENO out=./out$LINENO cat >"$in" <<\__END__ echo bar 1 echo bar 2 echo bar 3 __END__ # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 __END__ test_oE 'combination of options and operand' -i +m --rcfile="rcfile1" history -d 2 -w "$out" -cF -r "$in" -s 'set' 2 echo [$?] history echo --- cat "$out" __IN__ 2 echo bar 2 3 set [0] 1 echo bar 1 2 echo bar 2 3 set 4 echo [$?] 5 history --- echo foo 1 echo foo 3 history -d 2 -w "$out" -cF -r "$in" -s 'set' 2 __OUT__ ) test_Oe -e 2 'too many operands' history 1 2 __IN__ history: too many operands are specified __ERR__ test_Oe -e 2 'invalid option' history --no-such-option __IN__ history: `--no-such-option' is not a valid option __ERR__ #` ( export histfile=histfile$LINENO histsize=100 test_O -d -e 1 'printing to closed stream' -i +m --rcfile="rcfile1" : history >&- __IN__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/history1-y.tst000066400000000000000000000107111354143602500163450ustar00rootroot00000000000000# history1-y.tst: yash-specific test of history, part 1 if ! testee -c 'command -bv fc history' >/dev/null; then skip="true" fi cat >rcfile1 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile HISTSIZE=$histsize unset HISTRMDUP __END__ cat >rcfile2 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile HISTSIZE=$histsize HISTRMDUP=$histrmdup __END__ ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 __END__ test_OE -e 0 'history is saved when shell exits' -i +m --rcfile="rcfile1" test -s $histfile __IN__ ) ( export histfile=histfile$LINENO histsize=100 # Prepare the first history entry w/o running a test case. testee -is +m --rcfile="rcfile1" >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 __END__ test_oE -e 0 'empty lines are not saved in history' -i +m --rcfile="rcfile1" fc -l __IN__ 1 echo foo 1 2 echo foo 2 3 echo foo 3 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=50 test_OE -e 0 'listing empty history (-l)' -i +m --rcfile="rcfile1" history -c; fc -l __IN__ ) ( export histfile=histfile$LINENO histsize=50 test_Oe -e 1 're-execution with empty history (-s)' -i +m --rcfile="rcfile1" history -c; fc -s __IN__ fc: the command history is empty __ERR__ ) ( export histfile=histfile$LINENO histsize=100 test_oE 'HISTRMDUP unset' -i +m --rcfile="rcfile1" echo foo echo foo echo foo fc -l __IN__ foo foo foo 1 echo foo 2 echo foo 3 echo foo 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=100 histrmdup=x test_oE 'HISTRMDUP not a number' -i +m --rcfile="rcfile2" echo foo echo foo echo foo fc -l __IN__ foo foo foo 1 echo foo 2 echo foo 3 echo foo 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=100 histrmdup=0 test_oE 'HISTRMDUP=0' -i +m --rcfile="rcfile2" echo foo echo foo echo foo fc -l __IN__ foo foo foo 1 echo foo 2 echo foo 3 echo foo 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=100 histrmdup=1 test_oE 'HISTRMDUP=1' -i +m --rcfile="rcfile2" echo foo echo foo fc -l echo foo fc -l __IN__ foo foo 1 echo foo 2 fc -l foo 1 echo foo 2 fc -l 3 echo foo 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=100 histrmdup=2 test_oE 'HISTRMDUP=2' -i +m --rcfile="rcfile2" echo foo echo foo fc -l echo foo fc -l echo bar echo foo fc -l __IN__ foo foo 1 echo foo 2 fc -l foo 3 echo foo 4 fc -l bar foo 3 echo foo 4 fc -l 5 echo bar 6 echo foo 7 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=100 histrmdup=3 test_oE 'HISTRMDUP with similar commands' -i +m --rcfile="rcfile2" echo foo echo fo echo f fc -l __IN__ foo fo f 1 echo foo 2 echo fo 3 echo f 4 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=5 histrmdup=5 test_oE 'HISTRMDUP=HISTSIZE' -i +m --rcfile="rcfile2" fc -l echo 2 echo 3 echo 4 echo 5 fc -l __IN__ 1 fc -l 2 3 4 5 2 echo 2 3 echo 3 4 echo 4 5 echo 5 6 fc -l __OUT__ ) ( export histfile=histfile$LINENO histsize=5 histrmdup=6 test_oE 'HISTRMDUP=HISTSIZE+1' -i +m --rcfile="rcfile2" fc -l echo 2 echo 3 echo 4 echo 5 fc -l __IN__ 1 fc -l 2 3 4 5 2 echo 2 3 echo 3 4 echo 4 5 echo 5 6 fc -l __OUT__ ) ( export histfile=/dev/null histsize=100 # History is remembered even if the history file is not a regular file. test_oE 'null history file' -i +m --rcfile="rcfile1" echo foo history __IN__ foo 1 echo foo 2 history __OUT__ ) ( export histfile=inaccessible histsize=100 >"$histfile" chmod a= "$histfile" # History is remembered even if the history file is not a regular file. test_oE 'null history file' -i +m --rcfile="rcfile1" echo foo history __IN__ foo 1 echo foo 2 history __OUT__ ) ( export histfile=/dev/null histsize=1 # If the /dev/stdin special file is available, use it to speed up the test. # The shell enables buffering if it reads from a file. if [ "$(echo ok | cat /dev/stdin 2>/dev/null)" = ok ]; then export STDIN=/dev/stdin else unset STDIN fi test_OE -e 0 'history entry number wraps' { echo : while echo fc -l; do : done } | "$TESTEE" -i +m --rcfile="rcfile1" ${STDIN-} | grep -Fqx '1 fc -l' __IN__ ) ( export histfile=histfile$LINENO histsize=50 # Prepare the first history entry w/o running a test case. [ "${skip-}" ] || testee -is +m --rcfile="rcfile1" -o histspace >/dev/null <<\__END__ echo foo 1 echo foo 2 echo foo 3 echo foo 4 echo foo 5 __END__ test_oE -e 0 'histspace option' -i +m --rcfile="rcfile1" : bar fc -l __IN__ 1 echo foo 2 2 echo foo 4 3 : bar 4 fc -l __OUT__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/history2-y.tst000066400000000000000000000015041354143602500163460ustar00rootroot00000000000000# history2-y.tst: yash-specific test of history, part 2 if ! testee -c 'command -bv fc history' >/dev/null; then skip="true" fi cat >rcfile1 <<\__END__ PS1= PS2= HISTFILE=$PWD/$histfile HISTSIZE=$histsize unset HISTRMDUP __END__ ( export histfile=histfile$LINENO histsize=30 test_oE 'many history entries' ( i=32767 # < HISTORY_MIN_MAX_NUMBER while [ $i -gt 0 ]; do echo : $(( i-- )) done echo history ) | "$TESTEE" -i +m --rcfile="rcfile1" __IN__ 32739 : 29 32740 : 28 32741 : 27 32742 : 26 32743 : 25 32744 : 24 32745 : 23 32746 : 22 32747 : 21 32748 : 20 32749 : 19 32750 : 18 32751 : 17 32752 : 16 32753 : 15 32754 : 14 32755 : 13 32756 : 12 32757 : 11 32758 : 10 32759 : 9 32760 : 8 32761 : 7 32762 : 6 32763 : 5 32764 : 4 32765 : 3 32766 : 2 32767 : 1 32768 history __OUT__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/if-p.tst000066400000000000000000000171021354143602500151510ustar00rootroot00000000000000# if-p.tst: test of if conditional construct for any POSIX-compliant shell posix="true" test_oE 'execution path of if, true' if echo foo; then echo bar; fi __IN__ foo bar __OUT__ test_oE 'execution path of if, false' if ! echo foo; then echo bar; fi __IN__ foo __OUT__ test_oE 'execution path of if-else, true' if echo foo; then echo bar; else echo baz; fi __IN__ foo bar __OUT__ test_oE 'execution path of if-else, false' if ! echo foo; then echo bar; else echo baz; fi __IN__ foo baz __OUT__ test_oE 'execution path of if-elif, true' if echo 1; then echo 2; elif echo 3; then echo 4; fi __IN__ 1 2 __OUT__ test_oE 'execution path of if-elif, false-true' if ! echo 1; then echo 2; elif echo 3; then echo 4; fi __IN__ 1 3 4 __OUT__ test_oE 'execution path of if-elif, false-false' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; fi __IN__ 1 3 __OUT__ test_oE 'execution path of if-elif-else, true' if echo 1; then echo 2; elif echo 3; then echo 4; else echo 5; fi __IN__ 1 2 __OUT__ test_oE 'execution path of if-elif-else, false-true' if ! echo 1; then echo 2; elif echo 3; then echo 4; else echo 5; fi __IN__ 1 3 4 __OUT__ test_oE 'execution path of if-elif-else, false-false' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; else echo 5; fi __IN__ 1 3 5 __OUT__ test_oE 'execution path of if-elif-elif, true' if echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; fi __IN__ 1 2 __OUT__ test_oE 'execution path of if-elif-elif, false-true' if ! echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; fi __IN__ 1 3 4 __OUT__ test_oE 'execution path of if-elif-elif, false-false-true' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif echo 5; then echo 6; fi __IN__ 1 3 5 6 __OUT__ test_oE 'execution path of if-elif-elif, false-false-false' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif ! echo 5; then echo 6; fi __IN__ 1 3 5 __OUT__ test_oE 'execution path of if-elif-elif-else, true' if echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi __IN__ 1 2 __OUT__ test_oE 'execution path of if-elif-elif-else, false-true' if ! echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi __IN__ 1 3 4 __OUT__ test_oE 'execution path of if-elif-elif-else, false-false-true' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi __IN__ 1 3 5 6 __OUT__ test_oE 'execution path of if-elif-elif-else, false-false-false' if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif ! echo 5; then echo 6; else echo 7; fi __IN__ 1 3 5 7 __OUT__ ( setup <<\__END__ \unalias \x x() { return $1; } __END__ test_x -e 0 'exit status of if, true-true' if x 0; then x 0; fi __IN__ test_x -e 1 'exit status of if, true-false' if x 0; then x 1; fi __IN__ test_x -e 0 'exit status of if, false' if x 1; then x 2; fi __IN__ test_x -e 0 'exit status of if-else, true-true' if x 0; then x 0; else x 1; fi __IN__ test_x -e 1 'exit status of if-else, true-false' if x 0; then x 1; else x 2; fi __IN__ test_x -e 0 'exit status of if-else, false-true' if x 1; then x 2; else x 0; fi __IN__ test_x -e 2 'exit status of if-else, false-false' if x 1; then x 0; else x 2; fi __IN__ test_x -e 0 'exit status of if-elif, true-true' if x 0; then x 0; elif x 1; then x 2; fi __IN__ test_x -e 1 'exit status of if-elif, true-false' if x 0; then x 1; elif x 2; then x 3; fi __IN__ test_x -e 0 'exit status of if-elif, false-true-true' if x 1; then x 2; elif x 0; then x 0; fi __IN__ test_x -e 3 'exit status of if-elif, false-true-false' if x 1; then x 2; elif x 0; then x 3; fi __IN__ test_x -e 0 'exit status of if-elif-elif-else, true-true' if x 0; then x 0; elif x 1; then x 2; elif x 3; then x 4; else x 5; fi __IN__ test_x -e 11 'exit status of if-elif-elif-else, true-false' if x 0; then x 11; elif x 1; then x 2; elif x 3; then x 4; else x 5; fi __IN__ test_x -e 0 'exit status of if-elif-elif-else, false-true-true' if x 1; then x 2; elif x 0; then x 0; elif x 3; then x 4; else x 5; fi __IN__ test_x -e 13 'exit status of if-elif-elif-else, false-true-false' if x 1; then x 2; elif x 0; then x 13; elif x 3; then x 4; else x 5; fi __IN__ test_x -e 0 'exit status of if-elif-elif-else, false-false-true-true' if x 1; then x 2; elif x 3; then x 4; elif x 0; then x 0; else x 5; fi __IN__ test_x -e 5 'exit status of if-elif-elif-else, false-false-true-false' if x 1; then x 2; elif x 3; then x 4; elif x 0; then x 5; else x 6; fi __IN__ test_x -e 0 'exit status of if-elif-elif-else, false-false-false-true' if x 1; then x 2; elif x 3; then x 4; elif x 5; then x 6; else x 0; fi __IN__ test_x -e 7 'exit status of if-elif-elif-else, false-false-false-false' if x 1; then x 2; elif x 3; then x 4; elif x 5; then x 6; else x 7; fi __IN__ ) test_oE 'linebreak after if' if echo foo;then echo bar;fi __IN__ foo bar __OUT__ test_oE 'linebreak before then (after if)' if echo foo then echo bar;fi __IN__ foo bar __OUT__ test_oE 'linebreak after then (after if)' if echo foo;then echo bar;fi __IN__ foo bar __OUT__ test_oE 'linebreak before fi (after then)' if echo foo;then echo bar fi __IN__ foo bar __OUT__ test_oE 'linebreak before elif' if ! echo foo;then echo bar elif echo baz;then echo qux;fi __IN__ foo baz qux __OUT__ test_oE 'linebreak after elif' if ! echo foo;then echo bar;elif echo baz;then echo qux;fi __IN__ foo baz qux __OUT__ test_oE 'linebreak before then (after elif)' if ! echo foo;then echo bar;elif echo baz then echo qux;fi __IN__ foo baz qux __OUT__ test_oE 'linebreak after then (after elif)' if ! echo foo;then echo bar;elif echo baz;then echo qux;fi __IN__ foo baz qux __OUT__ test_oE 'linebreak before else' if ! echo foo;then echo bar else echo baz;fi __IN__ foo baz __OUT__ test_oE 'linebreak after else' if ! echo foo;then echo bar;else echo baz;fi __IN__ foo baz __OUT__ test_oE 'linebreak before fi (after else)' if ! echo foo;then echo bar;else echo baz fi __IN__ foo baz __OUT__ test_oE 'command ending with asynchronous command (after if)' if echo foo&then wait;fi __IN__ foo __OUT__ test_oE 'command ending with asynchronous command (after then)' if echo foo;then echo bar&fi;wait __IN__ foo bar __OUT__ test_oE 'command ending with asynchronous command (after elif)' if ! echo foo;then echo bar;elif echo baz&then wait;fi __IN__ foo baz __OUT__ test_oE 'command ending with asynchronous command (after else)' if ! echo foo;then echo bar;elif ! echo baz;then echo qux;else echo quux;fi;wait __IN__ foo baz quux __OUT__ test_oE 'more than one inner command' if echo 1; echo 2 echo 3; ! echo 4; then echo x1; echo x2 echo x3; echo x4; elif echo 5; echo 6 echo 7; echo 8; then echo 9; echo 10 echo 11; echo 12; else echo x5; echo x6 echo x7; echo x8; fi __IN__ 1 2 3 4 5 6 7 8 9 10 11 12 __OUT__ test_oE 'nest between if and then' if { echo foo; } then echo bar; fi __IN__ foo bar __OUT__ test_oE 'nest between then and fi' if echo foo; then { echo bar; } fi __IN__ foo bar __OUT__ test_oE 'nest between then and elif' if echo foo; then { echo bar; } elif echo baz; then echo qux; fi __IN__ foo bar __OUT__ test_oE 'nest between elif and then' if echo foo; then echo bar; elif { echo baz; } then echo qux; fi __IN__ foo bar __OUT__ test_oE 'nest between then and else' if ! echo foo; then { echo bar; } else echo baz; fi __IN__ foo baz __OUT__ test_oE 'nest between then and else' if ! echo foo; then echo bar; else { echo baz; } fi __IN__ foo baz __OUT__ test_oE 'redirection on if' if echo foo then echo bar else echo baz fi >redir_out cat redir_out __IN__ foo bar __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/if-y.tst000066400000000000000000000150751354143602500151710ustar00rootroot00000000000000# if-y.tst: yash-specific test of if conditional construct test_oE 'effect of empty condition (if, true)' true if then echo true; else echo false; fi __IN__ true __OUT__ test_oE 'effect of empty condition (if, false)' false if then echo true; else echo false; fi __IN__ true __OUT__ test_oE 'effect of empty condition (elif, false)' false if false; then echo X; elif then echo true; else echo false; fi __IN__ true __OUT__ test_E -e 0 'exit status of empty body (if, true)' false if true; then fi __IN__ test_E -e 0 'exit status of empty body (if-else, true)' false if true; then else fi __IN__ test_E -e 17 'exit status of empty body (if-else, false)' if(exit 17)then else fi __IN__ test_E -e 0 'exit status of empty body (if-elif, false-true)' if false; then elif true; then fi __IN__ test_E -e 19 'exit status of empty body (if-elif-else, false-false)' if false; then elif(exit 19)then else fi __IN__ # TODO: this behavior seems contradictory to the results of above tests test_E -e 23 'exit status of empty condition and body' (exit 23) if then elif then else fi __IN__ ( posix="true" test_Oe -e 2 'POSIX: empty condition (if, single line)' if then echo not reached; fi __IN__ syntax error: commands are missing between `if' and `then' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty condition (if, multi-line)' if then echo not reached; fi __IN__ syntax error: commands are missing between `if' and `then' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (then, single line)' if true; then fi __IN__ syntax error: commands are missing after `then' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (then, multi-line)' if true; then fi __IN__ syntax error: commands are missing after `then' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (else, single line)' if true; then echo not reached; else fi __IN__ syntax error: commands are missing after `else' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (else, multi-line)' if true; then echo not reached; else fi __IN__ syntax error: commands are missing after `else' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty condition (elif, single line)' if false; then echo not reached 1; elif then echo not reached 2; fi __IN__ syntax error: commands are missing between `elif' and `then' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty condition (elif, multi-line)' if false; then echo not reached 1; elif then echo not reached 2; fi __IN__ syntax error: commands are missing between `elif' and `then' __ERR__ #' #` #' #` ) test_Oe -e 2 'unpaired then (direct)' then :; fi __IN__ syntax error: encountered `then' without a matching `if' or `elif' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired then (after then)' if echo not reached 1; then echo not reached 2; then :; fi __IN__ syntax error: encountered `then' without a matching `if' or `elif' syntax error: (maybe you missed `fi'?) __ERR__ #' #` #' #` #' #` #' #` test_Oe -e 2 'unpaired fi (direct)' fi __IN__ syntax error: encountered `fi' without a matching `if' and/or `then' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired fi (after if)' if echo not reached; fi __IN__ syntax error: encountered `fi' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` #' #` test_Oe -e 2 'unpaired fi (after elif)' if echo not reached 1; then echo not reached 2; elif echo not reached 3; fi __IN__ syntax error: encountered `fi' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` #' #` test_Oe -e 2 'unpaired elif (direct)' elif echo not reached 1; then echo not reached 2; fi __IN__ syntax error: encountered `elif' without a matching `if' and/or `then' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired elif (after if)' if echo not reached 0; elif echo not reached 1; then echo not reached 2; fi __IN__ syntax error: encountered `elif' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired elif (after then)' if echo not reached 0; elif echo not reached 1; then echo not reached 2; fi __IN__ syntax error: encountered `elif' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired else (direct)' else echo not reached; fi __IN__ syntax error: encountered `else' without a matching `if' and/or `then' __ERR__ #' #` #' #` #' #` test_Oe -e 2 'unpaired else (after if)' if echo not reached 0; else echo not reached 1; fi __IN__ syntax error: encountered `else' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` #' #` test_Oe -e 2 'unpaired else (after elif)' if echo not reached 0; then echo not reached 1; elif echo not reached 2; else echo not reached 3; fi __IN__ syntax error: encountered `else' without a matching `if' and/or `then' syntax error: (maybe you missed `then'?) __ERR__ #' #` #' #` #' #` #' #` test_Oe -e 2 'missing then-fi (after if)' if echo not reached __IN__ syntax error: `then' is missing syntax error: `fi' is missing __ERR__ #' #` #' #` test_Oe -e 2 'missing then-fi (after elif)' if echo not reached 0; then echo not reached 1; elif echo not reached 2 __IN__ syntax error: `then' is missing syntax error: `fi' is missing __ERR__ #' #` #' #` test_Oe -e 2 'missing fi (after if-then)' if echo not reached 0; then echo not reached 1 __IN__ syntax error: `fi' is missing __ERR__ #' #` test_Oe -e 2 'missing fi (after elif-then)' if echo not reached 0; then echo not reached 1 elif echo not reached 2; then echo not reached 3 __IN__ syntax error: `fi' is missing __ERR__ #' #` test_Oe -e 2 'missing then (after if, in grouping)' { if } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `then'?) syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `fi'?) __ERR__ #' #` #' #` #' #` #' #` #' #` #' #` test_Oe -e 2 'missing then (after elif, in grouping)' { if :; then :; elif } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `then'?) syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `fi'?) __ERR__ #' #` #' #` #' #` #' #` #' #` #' #` test_Oe -e 2 'missing fi (after then, in grouping)' { if :; then } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `fi'?) __ERR__ #' #` #' #` #' #` test_Oe -e 2 'missing fi (after else, in grouping)' { if :; then :; else } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `fi'?) __ERR__ #' #` #' #` #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/input-p.tst000066400000000000000000000134501354143602500157140ustar00rootroot00000000000000# input-p.tst: test of input processing for any POSIX-compliant shell posix="true" # Note that this test case depends on the fact that run-test.sh passes the # input using a regular file. The test would fail if the input was not # seekable. See also the "Input files" section in POSIX.1-2008, 1.4 Utility # Description Defaults. test_oE 'no input more than needed is read' "$TESTEE" -c 'read -r line && printf "%s\n" "$line"' echo - this line is consumed by read and printed by printf echo - this line is consumed and executed by shell __IN__ echo - this line is consumed by read and printed by printf - this line is consumed and executed by shell __OUT__ test_x -e 0 'exit status of empty input' __IN__ test_x -e 0 'exit status of input containing blank lines only' __IN__ test_x -e 0 'exit status of input containing blank lines and comments only' # foo # bar __IN__ test_oE 'long line' echo 1 2 __IN__ 1 2 __OUT__ test_oE 'line continuation and long line' echo \ 1 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 2 __IN__ 1 2 __OUT__ printf 'alias false=:\nfalse\n' >inputfile.sh test_x -e 0 'shell input is line-wise (file)' ./inputfile.sh __IN__ test_x -e 0 'shell input is line-wise (standard input)' alias false=: false __IN__ test_x -e 0 'shell input is line-wise (-c)' -c 'alias false=: false' __IN__ test_x -e 0 'shell input is line-wise (command substitution)' x=$(alias false=: false) __IN__ test_x -e 0 'shell input is line-wise (eval)' eval 'alias false=: false' __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/job-p.tst000066400000000000000000000023041354143602500153230ustar00rootroot00000000000000# job-p.tst: test of job control for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% posix="true" mkfifo sync test_x -e 17 'job result is not lost when reported automatically (+b)' -im exec >sync && exit 17 & pid=$! cat sync : : : wait $pid __IN__ # This test is in async-p.tst. #test_oE 'stdin of asynchronous list is null without job control' +m test_oE 'stdin of asynchronous list is not modified with job control' -m tail -n 1& wait echo this line should be skipped by tail echo this line should be printed by tail __IN__ echo this line should be printed by tail __OUT__ # These tests are in async-p.tst. #test_oE -e 0 'asynchronous list ignores SIGINT' #test_oE -e 0 'asynchronous list ignores SIGQUIT' test_oE 'asynchronous list retains SIGINT trap with job control' -m "$TESTEE" -c 'kill -s INT $$; echo not printed' & wait $! kill -l $? trap '' INT "$TESTEE" -c 'kill -s INT $$; echo ok' & wait $! __IN__ INT ok __OUT__ test_oE 'asynchronous list retains SIGQUIT trap with job control' -m "$TESTEE" -c 'kill -s QUIT $$; echo not printed' & wait $! kill -l $? trap '' QUIT "$TESTEE" -c 'kill -s QUIT $$; echo ok' & wait $! __IN__ QUIT ok __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/job-y.tst000066400000000000000000000006201354143602500153330ustar00rootroot00000000000000# job-y.tst: yash-specific test of job control ../checkfg || skip="true" # %REQUIRETTY% mkfifo sync # According to POSIX, a shell may, but is not required to, forget the job # when the -b option is on. Yash forgets it. test_x -e 17 'job result is not lost when reported automatically (-b)' -bim exec >sync && exit 17 & pid=$! cat sync : : : wait $pid __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/jobs-y.tst000066400000000000000000000036411354143602500155240ustar00rootroot00000000000000# jobs-y.tst: yash-specific test of the jobs & suspend built-ins ../checkfg || skip="true" # %REQUIRETTY% test_oE 'suspend: suspending' -m "$TESTEE" -c 'suspend; echo $?' echo - fg >/dev/null __IN__ - 0 __OUT__ test_oE 'jobs: printing jobs' -m +o curstop "$TESTEE" -c 'suspend; : 1' "$TESTEE" -c 'suspend; : 2' "$TESTEE" -c 'suspend; : 3' jobs echo \$?=$? while fg; do :; done >/dev/null 2>&1 __IN__ [1] Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : 1' [2] - Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : 2' [3] + Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : 3' $?=0 __OUT__ test_oE 'jobs: specifying job IDs' -m +o curstop "$TESTEE" -c 'suspend; : task-1' "$TESTEE" -c 'suspend; : task-2' "$TESTEE" -c 'suspend; : task-3' jobs %1 echo jobs 1 echo jobs %\?task-2 echo jobs \?task-2 echo \$?=$? while fg; do :; done >/dev/null 2>&1 __IN__ [1] Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : task-1' [1] Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : task-1' [2] - Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : task-2' [2] - Stopped(SIGSTOP) "${TESTEE}" -c 'suspend; : task-2' $?=0 __OUT__ test_oE 'exit status of suspended job' -m "$TESTEE" -cim --norcfile 'echo 1; suspend; echo 2' kill -l $? bg >/dev/null wait % kill -l $? fg >/dev/null __IN__ 1 STOP TTOU 2 __OUT__ test_Oe -e 1 'non-existing job number' jobs %100 __IN__ jobs: no such job `%100' __ERR__ #' #` test_Oe -e 1 'non-existing job name' jobs %no_such_job __IN__ jobs: no such job `%no_such_job' __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' jobs --no-such=option __IN__ jobs: `--no-such=option' is not a valid option __ERR__ #' #` test_O -d -e 1 'printing to closed stream' exec 3>>|4 (exec 3>&- && cat <&4)& # dummy command to be printed by "jobs" jobs >&- __IN__ ( posix=true test_Oe -e 1 'initial % cannot be omitted in POSIX mode' jobs foo __IN__ jobs: `foo' is not a valid job specification __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/kill-y.tst000066400000000000000000000104021354143602500155130ustar00rootroot00000000000000# kill-y.tst: yash-specific test of the kill built-in # $1 = LINENO, $2 = signal name w/o SIG test_printing_signal_name_from_name() { testcase "$1" -e 0 "printing signal name $2 from name" \ 3<<__IN__ 4<<__OUT__ 5/dev/null echo kill 21 $? kill -l -v >/dev/null echo kill 22 $? kill -v -l >/dev/null echo kill 23 $? kill -lv >/dev/null echo kill 24 $? kill -vl >/dev/null echo kill 25 $? kill -v >/dev/null echo kill 26 $? kill -l -- 3 9 15 echo kill 27 $? kill -lv -- 3 9 15 >/dev/null echo kill 28 $? kill -s chld $$ $$ echo kill 29 $? kill -schld $$ $$ echo kill 30 $? kill -s SIGCHLD $$ $$ echo kill 31 $? kill -sSIGCHLD $$ $$ echo kill 32 $? kill -s sigchld $$ $$ echo kill 33 $? kill -ssigchld $$ $$ echo kill 34 $? kill -SIGCHLD $$ $$ echo kill 35 $? kill -SiGcHlD $$ $$ echo kill 36 $? __IN__ kill 1 0 kill 2 0 kill 3 0 kill 4 0 kill 5 0 kill 6 0 kill 7 0 kill 8 0 kill 9 0 kill 10 0 kill 11 0 kill 12 0 kill 13 0 kill 14 0 kill 15 0 kill 16 0 kill 17 0 kill 18 0 kill 19 0 kill 20 0 kill 21 0 kill 22 0 kill 23 0 kill 24 0 kill 25 0 kill 26 0 QUIT KILL TERM kill 27 0 kill 28 0 kill 29 0 kill 30 0 kill 31 0 kill 32 0 kill 33 0 kill 34 0 kill 35 0 kill 36 0 __OUT__ test_Oe -e 1 'invalid option' kill --no-such-option __IN__ kill: no such signal `-NO-SUCH-OPTION' __ERR__ #' #` test_Oe -e 2 'specifying -l and -n both' kill -l -n 0 __IN__ kill: the -n option cannot be used with the -l option __ERR__ test_Oe -e 2 'specifying -l and -s both' kill -l -s INT __IN__ kill: the -s option cannot be used with the -l option __ERR__ test_Oe -e 2 'missing operand' kill __IN__ kill: this command requires an operand __ERR__ test_Oe -e 1 'printing name of unknown signal' kill -l 0 __IN__ kill: no such signal `0' __ERR__ #' #` test_Oe -e 1 'sending signal to unknown job' kill %100 __IN__ kill: no such job `%100' __ERR__ #' #` test_O -d -e 1 'printing to closed stream (no operand)' kill -l >&- __IN__ test_O -d -e 1 'printing to closed stream (with operand)' kill -l 1 >&- __IN__ test_Oe -e 2 'signal name must be specified w/o SIG (POSIX)' --posix kill -s SIGTERM $$ __IN__ kill: SIGTERM: the signal name must be specified without `SIG' __ERR__ #' #` ( if ! testee --version --verbose | grep -Fqx ' * help'; then skip="true" fi test_oE -e 0 'help' kill --help __IN__ kill: send a signal to processes Syntax: kill [-signal|-s signal|-n number] process... kill -l [-v] [number...] Try `man yash' for details. __OUT__ #' #` ) test_Oe -e 1 'no help in POSIX mode' --posix kill --help __IN__ kill: no such signal `-HELP' __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/kill1-p.tst000066400000000000000000000031461354143602500155720ustar00rootroot00000000000000# kill1-p.tst: test of the kill built-in for any POSIX-compliant shell, part 1 posix="true" test_E -e 0 'printing all signal names' kill -l __IN__ # $1 = LINENO, $2 = signal number, $3 = signal name w/o SIG test_printing_signal_name_from_number() { testcase "$1" -e 0 "printing signal name $3 from number" \ 3<<__IN__ 4<<__OUT__ 5fifo | cat fifo fifo kill -l $? __IN__ HUP __OUT__ test_oE 'sending signal with negative process number: -s HUP' -m ( pgid="$(exec sh -c 'echo $PPID')" kill -s HUP -- -$pgid >fifo | cat fifo fifo ) kill -l $? __IN__ HUP __OUT__ test_oE 'sending signal with negative process number: -1' -m ( pgid="$(exec sh -c 'echo $PPID')" kill -1 -- -$pgid >fifo | cat fifo fifo ) kill -l $? __IN__ HUP __OUT__ ( setup 'halt() while kill -s CONT $$; do sleep 1; done' mkfifo fifo1 fifo2 fifo3 test_oE 'sending signal to background job' -m (trap 'echo 1; exit' USR1; >fifo1; halt) | (trap 'echo 2; cat; exit' USR1; >fifo2; halt) | (trap 'echo 3; cat; exit' USR1; >fifo3; halt) & halt & fifo1; halt X) & (trap 'echo; exit' TERM; >fifo2; halt Y) & dotscript <<\__END__ echo dot a $LINENO echo dot b $LINENO echo dot c $LINENO __END__ test_oE -e 0 'LINENO in and out of dot script' echo before $LINENO . ./dotscript echo after $LINENO __IN__ before 1 dot a 1 dot b 2 dot c 4 after 3 __OUT__ test_oE -e 0 'LINENO in eval script' echo before $LINENO eval 'echo eval a $LINENO echo eval b $LINENO echo eval c $LINENO' echo after $LINENO __IN__ before 1 eval a 1 eval b 2 eval c 4 after 7 __OUT__ test_oE -e 0 'LINENO and alias with newline' alias e='echo x $LINENO echo y $LINENO' echo a $LINENO e echo b $LINENO __IN__ a 3 x 4 y 5 b 6 __OUT__ # In this test the character sequence "$((" looks like the beginning of an # arithmetic expansion, but it does not have the corresponding "))", so the # expansion is re-parsed as a command substitution. test_oE -e 0 'LINENO after arithmetic-expansion-like command substitution' -s : $(($( \ )) ) echo $LINENO __IN__ 4 __OUT__ # )) # XXX This is like the above, but it is much harder to fix... : <<\__OUT__ test_oE -e 0 'LINENO in arithmetic-expansion-like command substitution' -s echo $((echo $( echo $LINENO \ )) ) echo $LINENO __IN__ 2 4 __OUT__ # )) test_o -e 0 'LINENO in interactive shell is reset for each command line' -i +m echo a $LINENO for i in 1 2; do echo $i \ $LINENO done echo b $LINENO { \ func () { echo f $LINENO } } func __IN__ a 1 1 2 2 2 b 1 f 4 __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/local-y.tst000066400000000000000000000033441354143602500156610ustar00rootroot00000000000000# local-y.tst: yash-specific test of the local built-in # Since "local" is an alias for "typeset", most tests in this file are # analogous to those in typeset-y.tst. test_oE -e 0 'local is a semi-special built-in' command -V local __IN__ local: a semi-special built-in __OUT__ test_oE -e 0 'defining variable in global namespace' -e local a=1 echo $a __IN__ 1 __OUT__ test_oE -e 0 'defining local variable' -e f() { local a=1 b=2 echo $a $b a=3 b=4 echo $a $b } f echo ${a-unset} ${b-unset} __IN__ 1 2 3 4 unset 4 __OUT__ test_oE -e 0 'only local variables are printed by default (no option)' -e f() { a=1; local; } g() { local a=1; local; } f echo --- g __IN__ --- local a=1 __OUT__ test_oE -e 0 'defining and printing local array (no option)' -e f() { local a a=(This is my array.) printf '%s\n' "$a" local } a=global f echo $a __IN__ This is my array. a=(This is my array.) local a global __OUT__ test_oE 'defining exported variables (-x)' -e f() { a=1 local -x a b=2 echo [$a] $b a=3 echo $a $b sh -c 'echo $a $b' } a=a b=b f echo $a $b __IN__ [] 2 3 2 3 2 1 b __OUT__ test_oE -e 0 'only local variables are printed by default (-p)' -e f() { a=1; local -p; } g() { local a=1; local -p; } f echo --- g __IN__ --- local a=1 __OUT__ test_Oe -e 2 'invalid option -f' local -f __IN__ local: `-f' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option -g' local -g __IN__ local: `-g' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' local --global __IN__ local: `--global' is not a valid option __ERR__ #' #` test_Oe -e 2 'specifying -x and -X at once' local -xX __IN__ local: the -x option cannot be used with the -X option __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/nop-p.tst000066400000000000000000000004531354143602500153500ustar00rootroot00000000000000# nop-p.tst: test of the colon, true, and false built-ins posix="true" test_OE -e 0 'colon (no arguments)' : __IN__ test_OE -e 0 'colon (some arguments)' : unused ignored arguments __IN__ test_OE -e 0 'true' true __IN__ test_OE -e n 'false' false __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/option-p.tst000066400000000000000000000172311354143602500160660ustar00rootroot00000000000000# option-p.tst: test of shell options for any POSIX-compliant shell posix="true" test_x -e 0 'allexport (short) on: $-' -a printf '%s\n' "$-" | grep -q a __IN__ test_x -e 0 'allexport (long) on: $-' -o allexport printf '%s\n' "$-" | grep -q a __IN__ test_x -e 0 'allexport (short) off: $-' +a printf '%s\n' "$-" | grep -qv a __IN__ test_x -e 0 'allexport (long) off: $-' +o allexport printf '%s\n' "$-" | grep -qv a __IN__ test_oE 'allexport (short) on: effect' -a unset foo foo=bar sh -c 'echo ${foo-unset}' __IN__ bar __OUT__ test_oE 'allexport (long) on: effect' -o allexport unset foo foo=bar sh -c 'echo ${foo-unset}' __IN__ bar __OUT__ test_oE 'allexport (short) off: effect' +a unset foo foo=bar sh -c 'echo ${foo-unset}' __IN__ unset __OUT__ test_oE 'allexport (long) off: effect' +o allexport unset foo foo=bar sh -c 'echo ${foo-unset}' __IN__ unset __OUT__ # XXX: test of the -b option is not supported test_x -e 0 'errexit (short) on: $-' -e printf '%s\n' "$-" | grep -q e __IN__ test_x -e 0 'errexit (long) on: $-' -o errexit printf '%s\n' "$-" | grep -q e __IN__ test_x -e 0 'errexit (short) off: $-' +e printf '%s\n' "$-" | grep -qv e __IN__ test_x -e 0 'errexit (long) off: $-' +o errexit printf '%s\n' "$-" | grep -qv e __IN__ # Other tests of the -e option are in errexit-p.tst. test_x -e 0 'noglob (short) on: $-' -f printf '%s\n' "$-" | grep -q f __IN__ test_x -e 0 'noglob (long) on: $-' -o noglob printf '%s\n' "$-" | grep -q f __IN__ test_x -e 0 'noglob (short) off: $-' +f printf '%s\n' "$-" | grep -qv f __IN__ test_x -e 0 'noglob (long) off: $-' +o noglob printf '%s\n' "$-" | grep -qv f __IN__ test_oE 'noglob (short) on: effect' -f echo /* __IN__ /* __OUT__ test_oE 'noglob (long) on: effect' -o noglob echo /* __IN__ /* __OUT__ test_oE 'noglob (short) off: effect' +f printf '%s\n' /* | grep -x /dev __IN__ /dev __OUT__ test_oE 'noglob (long) off: effect' +o noglob printf '%s\n' /* | grep -x /dev __IN__ /dev __OUT__ test_x -e 0 'hashondef (short) on: $-' -h printf '%s\n' "$-" | grep -q h __IN__ test_x -e 0 'hashondef (short) off: $-' +h printf '%s\n' "$-" | grep -qv h __IN__ test_oE 'hashondef (short) on: effect' -h h_option_test() { cat /dev/null; } echo $(hash | grep '/cat$' | wc -l) __IN__ 1 __OUT__ # XXX: test of the -m option is not supported test_x -e 0 'noexec (short) on: $-' -n printf '%s\n' "$-" | grep -q n __IN__ test_x -e 0 'noexec (long) on: $-' -o noexec printf '%s\n' "$-" | grep -q n __IN__ test_x -e 0 'noexec (short) off: $-' +n printf '%s\n' "$-" | grep -qv n __IN__ test_x -e 0 'noexec (long) off: $-' +o noexec printf '%s\n' "$-" | grep -qv n __IN__ test_OE 'noexec (short) on: simple command is not executed' -n echo executed __IN__ test_OE 'noexec (long) on: simple command is not executed' -o noexec echo executed __IN__ test_oE 'noexec (short) off: simple command is executed' +n echo executed __IN__ executed __OUT__ test_oE 'noexec (long) off: simple command is executed' +o noexec echo executed __IN__ executed __OUT__ { testee -cn 'for i in $(>noexec_file); do :; done' test_OE -e 0 'noexec (short) on: for command is not executed' ! [ -e noexec_file ] __IN__ } test_x -e 0 'nounset (short) on: $-' -u printf '%s\n' "$-" | grep -q u __IN__ test_x -e 0 'nounset (long) on: $-' -o nounset printf '%s\n' "$-" | grep -q u __IN__ test_x -e 0 'nounset (short) off: $-' +u printf '%s\n' "$-" | grep -qv u __IN__ test_x -e 0 'nounset (long) off: $-' +o nounset printf '%s\n' "$-" | grep -qv u __IN__ ( setup -d setup 'foo=bar s=; unset x' test_oE -e 0 'nounset on: expansions of set variable' -u bracket ${foo} ${foo-unset} ${foo:-unset} ${foo+set} ${foo:+set} bracket "${s}" "${s-unset}" "${s:-unset}" "${s+set}" "${s:+set}" bracket ${#foo} ${#s} bracket ${foo#b} ${foo##b} ${foo%r} ${foo%%r} bracket "${s#b}" "${s##b}" "${s%r}" "${s%%r}" __IN__ [bar][bar][bar][set][set] [][][unset][set][] [3][0] [ar][ar][ba][ba] [][][][] __OUT__ test_oE -e 0 'nounset on: set variable ${foo=bar}' -u bracket ${foo=X} bracket ${foo} __IN__ [bar] [bar] __OUT__ test_oE -e 0 'nounset on: set variable ${foo:=bar}' -u bracket ${foo:=X} bracket ${foo} __IN__ [bar] [bar] __OUT__ test_oE -e 0 'nounset on: set variable ${foo?bar}' -u bracket ${foo?X} __IN__ [bar] __OUT__ test_oE -e 0 'nounset on: set variable ${foo:?bar}' -u bracket ${foo:?X} __IN__ [bar] __OUT__ test_oE -e 0 'nounset on: set variable $((foo))' -u bracket $((x=42)) bracket $((x)) __IN__ [42] [42] __OUT__ test_oE -e 0 'nounset on: empty variable ${foo=bar}' -u bracket "${s=X}" bracket "${s}" __IN__ [] [] __OUT__ test_oE -e 0 'nounset on: empty variable ${foo:=bar}' -u bracket "${s:=X}" bracket "${s}" __IN__ [X] [X] __OUT__ test_oE -e 0 'nounset on: empty variable ${foo?bar}' -u bracket "${s?X}" __IN__ [] __OUT__ test_O -d -e n 'nounset on: empty variable ${foo:?bar}' -u bracket "${s:?X}" bracket "${s}" __IN__ test_O -d -e n 'nounset on: unset variable ${foo}' -u bracket ${x} __IN__ test_oE -e 0 'nounset on: unset variable ${foo-bar}' -u bracket ${x-unset} __IN__ [unset] __OUT__ test_oE -e 0 'nounset on: unset variable ${foo:-bar}' -u bracket ${x:-unset} __IN__ [unset] __OUT__ test_oE -e 0 'nounset on: unset variable ${foo+bar}' -u bracket "${x+set}" __IN__ [] __OUT__ test_oE -e 0 'nounset on: unset variable ${foo:+bar}' -u bracket "${x:+set}" __IN__ [] __OUT__ test_oE -e 0 'nounset on: unset variable ${foo=bar}' -u bracket ${x=unset} bracket ${x} __IN__ [unset] [unset] __OUT__ test_oE -e 0 'nounset on: unset variable ${foo:=bar}' -u bracket ${x:=unset} bracket ${x} __IN__ [unset] [unset] __OUT__ test_O -d -e n 'nounset on: unset variable ${foo?bar}' -u bracket ${x?unset} __IN__ test_O -d -e n 'nounset on: unset variable ${foo:?bar}' -u bracket ${x:?unset} __IN__ test_O -d -e n 'nounset on: unset variable ${#foo}' -u bracket "${#x}" __IN__ test_O -d -e n 'nounset on: unset variable ${foo#bar}' -u bracket "${x#y}" __IN__ test_O -d -e n 'nounset on: unset variable ${foo##bar}' -u bracket "${x##y}" __IN__ test_O -d -e n 'nounset on: unset variable ${foo%bar}' -u bracket "${x%y}" __IN__ test_O -d -e n 'nounset on: unset variable ${foo%%bar}' -u bracket "${x%%y}" __IN__ test_O -d -e n 'nounset on: unset variable $((foo))' -u bracket "$((x))" __IN__ ) test_x -e 0 'verbose (short) on: $-' -v printf '%s\n' "$-" | grep -q v __IN__ test_x -e 0 'verbose (long) on: $-' -o verbose printf '%s\n' "$-" | grep -q v __IN__ test_x -e 0 'verbose (short) off: $-' +v printf '%s\n' "$-" | grep -qv v __IN__ test_x -e 0 'verbose (long) off: $-' +o verbose printf '%s\n' "$-" | grep -qv v __IN__ test_oe 'verbose (short) on: effect' -v echo 1 echo 2 if true; then echo 3 fi __IN__ 1 2 3 __OUT__ echo 1 echo 2 if true; then echo 3 fi __ERR__ test_oe 'verbose (long) on: effect' -o verbose echo 1 echo 2 if true; then echo 3 fi __IN__ 1 2 3 __OUT__ echo 1 echo 2 if true; then echo 3 fi __ERR__ test_x -e 0 'xtrace (short) on: $-' -x printf '%s\n' "$-" | grep -q x __IN__ test_x -e 0 'xtrace (long) on: $-' -o xtrace printf '%s\n' "$-" | grep -q x __IN__ test_x -e 0 'xtrace (short) off: $-' +x printf '%s\n' "$-" | grep -qv x __IN__ test_x -e 0 'xtrace (long) off: $-' +o xtrace printf '%s\n' "$-" | grep -qv x __IN__ test_oe 'xtrace (short) on: effect' -x foo=bar echo $foo __IN__ bar __OUT__ + foo=bar + echo bar __ERR__ test_oe 'xtrace (long) on: effect' -o xtrace foo=bar echo $foo __IN__ bar __OUT__ + foo=bar + echo bar __ERR__ test_oe '$PS4' foo=XY PS4='${foo#X} '; set -x 2>/dev/null echo xtrace __IN__ xtrace __OUT__ Y echo xtrace __ERR__ # To test the ignoreeof option, we need to emulate the terminal device. #test_oe 'ignoreeof on: effect' -o ignoreeof test_OE -e 0 'ignoreeof is ignored if not interactive' +i -o ignoreeof # The shell should exit successfully at EOF __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/option-y.tst000066400000000000000000000077441354143602500161070ustar00rootroot00000000000000# option-y.tst: yash-specific test of shell options test_x -e 0 'hashondef (long) on: $-' -o hashondef printf '%s\n' "$-" | grep -q h __IN__ test_x -e 0 'hashondef (long) off: $-' +o hashondef printf '%s\n' "$-" | grep -qv h __IN__ test_o 'noexec is linewise' set -n; echo executed echo not executed __IN__ executed __OUT__ test_o 'noexec is ineffective when interactive' -in +m --norcfile echo printed; exit; echo not printed __IN__ printed __OUT__ >Caseglob1 >caseglob2 test_oE 'caseglob on: effect' --caseglob echo caseglob* __IN__ caseglob2 __OUT__ test_oE 'caseglob off: effect' --nocaseglob echo caseglob* echo Caseglob* __IN__ Caseglob1 caseglob2 Caseglob1 caseglob2 __OUT__ ( mkdir dotglob cd dotglob >.dotglob ) ( setup 'cd dotglob' test_oE 'dotglob on: effect' --dotglob echo * echo ?dotglob __IN__ . .. .dotglob .dotglob __OUT__ test_oE 'dotglob off: effect' --nodotglob echo * echo ?dotglob __IN__ * ?dotglob __OUT__ ) ( mkdir markdirs cd markdirs >regular mkdir directory ) ( setup 'cd markdirs' test_oE 'markdirs on: effect' --markdirs echo *r* __IN__ directory/ regular __OUT__ test_oE 'markdirs off: effect' --nomarkdirs echo *r* __IN__ directory regular __OUT__ ) ( mkdir extendedglob cd extendedglob mkdir dir dir/dir2 dir/.dir2 anotherdir .dir .dir/dir2 >dir/dir2/file >dir/.dir2/file >anotherdir/file >.dir/file >.dir/dir2/file ln -s ../../anotherdir dir/dir2/link ln -s ../../anotherdir dir/dir2/.link ln -s ../dir anotherdir/loop ) ( setup 'cd extendedglob' test_oE 'extendedglob on: effect' --extendedglob echo **/file echo ***/file echo .**/file echo .***/file __IN__ anotherdir/file dir/dir2/file anotherdir/file anotherdir/loop/dir2/file dir/dir2/file dir/dir2/link/file .dir/dir2/file .dir/file anotherdir/file dir/.dir2/file dir/dir2/file .dir/dir2/file .dir/file anotherdir/file anotherdir/loop/.dir2/file anotherdir/loop/dir2/file dir/.dir2/file dir/dir2/.link/file dir/dir2/file dir/dir2/link/file __OUT__ test_oE 'extendedglob off: effect' --noextendedglob echo **/file echo ***/file echo .**/file echo .***/file __IN__ anotherdir/file anotherdir/file .dir/file .dir/file __OUT__ ) mkdir nullglob >nullglob/xxx ( setup -d setup 'cd nullglob' test_oE 'nullglob on: effect' --nullglob bracket n*ll f[o/b]r f?o/b*r x*x __IN__ [f[o/b]r][xxx] __OUT__ test_oE 'nullglob off: effect' --nonullglob bracket n*ll f[o/b]r f?o/b*r x*x __IN__ [n*ll][f[o/b]r][f?o/b*r][xxx] __OUT__ ) test_OE -e 0 'pipefail on: single command successful pipe' --pipefail true __IN__ test_OE -e 13 'pipefail on: single command unsuccessful pipe' --pipefail (exit 13) __IN__ test_OE -e 0 'pipefail on: multi-command successful pipe' --pipefail true | true | true | true __IN__ test_OE -e 7 'pipefail on: multi-command unsuccessful pipe' --pipefail true | exit 2 | true | exit 7 | true | true __IN__ test_OE -e 7 'pipefail on: multi-command unsuccessful pipe in subshell' \ --pipefail (true | exit 2 | true | exit 7 | true | true) __IN__ test_oE 'traceall on: effect' --traceall exec 2>&1 COMMAND_NOT_FOUND_HANDLER='echo not found $* >&2; HANDLED=1' set -xv no/such/command __IN__ no/such/command + no/such/command + echo not found no/such/command not found no/such/command + HANDLED=1 __OUT__ test_oE 'traceall on: effect' --notraceall exec 2>&1 COMMAND_NOT_FOUND_HANDLER='echo not found $* >&2; HANDLED=1' set -xv no/such/command __IN__ no/such/command + no/such/command not found no/such/command __OUT__ test_Oe -e 2 'unset off: unset variable $((foo))' -u eval '$((x))' __IN__ eval: arithmetic: parameter `x' is not set __ERR__ #' #` test_x -e 0 'abbreviation of -o argument' -o allex echo $- | grep -q a __IN__ test_x -e 0 'abbreviation of +o argument' -a +o allexport echo $- | grep -qv a __IN__ test_x -e 0 'concatenation of -o and argument' -oallexport echo $- | grep -q a __IN__ test_x -e 0 'concatenation of option and -o' -ao errexit echo $- | grep a | grep -q e __IN__ test_x -e 0 'concatenation of option and -o and argument' -aoerrexit echo $- | grep a | grep -q e __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/param-p.tst000066400000000000000000000217241354143602500156600ustar00rootroot00000000000000# param-p.tst: test of parameter expansion for any POSIX-compliant shell posix="true" > file setup -d test_oE 'format for parameter expansion' a=a bracket -${a-\}}- -${a-'}'}- -${a-"}"}- -${a}}- bracket -${a-${a}}- -${a-$({ :;})}- bracket -${a-$(($({ :;})))}- -${a-${a-${a-${a}}}}- __IN__ [-a-][-a-][-a-][-a}-] [-a-][-a-] [-a-][-a-] __OUT__ test_oE -e 0 'simplest expansion' a=value unset b bracket -${a}- bracket -${b}- __IN__ [-value-] [--] __OUT__ test_oE 'expansion w/o braces, variables' aa=value _L0NG_variable_name=x unset aaa bracket -$aa- bracket -$aaa- bracket -$_L0NG_variable_name- __IN__ [-value-] [--] [-x-] __OUT__ test_oE 'expansion w/o braces, positional parameters' set a b bracket -$1- $22 __IN__ [-a-][b2] __OUT__ test_oE 'expansion w/o braces, special parameters' set 1 : dummy& zero="$0" dollar="$$" hyphen="$-" ex="$!" hash="$#" star="$*" at="$@" echoraw "$??" [ "$00" = "${zero}0" ] || echoraw '$0' [ "$00" = "${zero}0" ] [ "$$$$" = "$dollar$dollar" ] || echoraw '$$' [ "$$$$" = "$dollar$dollar" ] [ "$--" = "$hyphen-" ] || echoraw '$-' [ "$--" = "$hyphen-" ] [ "$!!" = "$ex!" ] || echoraw '$!' [ "$!!" = "$ex!" ] [ "$##" = "$hash#" ] || echoraw '$#' [ "$##" = "$hash#" ] [ "$**" = "$star*" ] || echoraw '$*' [ "$**" = "$star*" ] [ "$@@" = "$at@" ] || echoraw '$@' [ "$@@" = "$at@" ] __IN__ 0? __OUT__ test_oE 'double-quoted expansion is not subject to pathname expansion' a='*' bracket "${a}" bracket ${a+"${a}"} __IN__ [*] [*] __OUT__ test_oE 'double-quoted expansion is not subject to field splitting' a='a b c' bracket "${a}" bracket ${a+"${a}"} __IN__ [a b c] [a b c] __OUT__ test_oE 'tilde expansion in embedded word' HOME=/foo/bar bracket ${a-~} ${a-~/} __IN__ [/foo/bar][/foo/bar/] __OUT__ test_oE 'parameter expansion in embedded word' b=b bracket ${a-x${b}x} __IN__ [xbx] __OUT__ test_oE 'command substitution in embedded word' bracket ${a-x$(echo -)x} __IN__ [x-x] __OUT__ test_oE 'arithmetic expansion in embedded word' bracket ${a-x$((1+1))x} __IN__ [x2x] __OUT__ test_oE 'embedded word is expanded only if needed' a=a unset b bracket -${a-${b?}}- -${b+${b?}}- -${a=${b?}}- -${a?${b?}}- __IN__ [-a-][--][-a-][-a-] __OUT__ test_oE 'end of embedded word' a=a bracket ${a-x}b} __IN__ [ab}] __OUT__ ( setup 'a=a n=; unset u' test_oE '${a-b}' bracket "${a-x}" "${n-x}" "${u-x}" bracket "${a:-x}" "${n:-x}" "${u:-x}" __IN__ [a][][x] [a][x][x] __OUT__ test_oE '${a+b}' bracket "${a+x}" "${n+x}" "${u+x}" bracket "${a:+x}" "${n:+x}" "${u:+x}" __IN__ [x][x][] [x][][] __OUT__ test_oE '${a=b}' bracket "${a=x}" "${n=x}" "${u=x}" bracket "${a}" "${n}" "${u}" __IN__ [a][][x] [a][][x] __OUT__ test_oE '${a:=b}' bracket "${a:=x}" "${n:=x}" "${u:=x}" bracket "${a}" "${n}" "${u}" __IN__ [a][x][x] [a][x][x] __OUT__ test_O -d -e n 'assigning to read-only variable' readonly n bracket ${n:=} __IN__ test_O -d -e n 'assigning to positional parameter' bracket ${1:=} __IN__ test_O -d -e n 'assigning to special parameter' bracket ${*:=} __IN__ test_oE '${a?b}, success' bracket "${a?x}" "${n?x}" bracket "${a:?x}" __IN__ [a][] [a] __OUT__ test_O -d -e n '${unset?b}, failure w/o message' bracket "${u?}" __IN__ test_O -e n '${unset?b}, failure with message, exit status and stdout' bracket "${u?foo bar baz}" __IN__ test_OE -e 0 '${unset?b}, failure with message, stderr' (: "${u?foo bar baz}") 2>&1 | grep -Fq 'foo bar baz' __IN__ test_O -d -e n '${null?b}, failure' bracket "${n:?}" __IN__ test_O -d -e n '${unset?b}, failure' bracket "${u:?}" __IN__ ) test_oE 'length of valid variables' zero= one=a two=bb five=ccccc twenty=dddddddddddddddddddd bracket ${#zero} ${#one} ${#five} ${#twenty} set '' a bb cccc bracket ${#1} ${#2} ${#3} ${#4} : dummy& zero="$0" dollar="$$" hyphen="$-" ex="$!" hash="$#" echoraw '${#?}' ${#?} [ "${#0}" = "${#zero}" ] || echoraw '$0' [ "${#0}" = "${#zero}" ] [ "${#$}" = "${#dollar}" ] || echoraw '$$' [ "${#$}" = "${#dollar}" ] [ "${#-}" = "${#hyphen}" ] || echoraw '$-' [ "${#-}" = "${#hyphen}" ] [ "${#!}" = "${#ex}" ] || echoraw '$!' [ "${#!}" = "${#ex}" ] # ambiguous... # [ "${##}" = "${#hash}" ] || echoraw '$#' [ "${##}" = "${#hash}" ] __IN__ [0][1][5][20] [0][1][2][4] ${#?} 1 __OUT__ test_oE -e 0 'length of unset variables, success' unset u echoraw ${#u} __IN__ 0 __OUT__ test_O -d -e n 'length of unset variables, failure' -u unset u echoraw ${#u} __IN__ test_oE 'disambiguation of ${#...' bracket ${#-""} bracket ${#?X} bracket ${#+""} "${#:+}" bracket ${#=""} "${#:=}" __IN__ [0] [0] [][] [0][0] __OUT__ test_oE 'removing shortest matching prefix' a=1-2-3-4 s='***' h='###' bracket "${a#1}" "${a#*1}" "${a#1*}" "${a#1*-}" bracket "${a#*-}" "${a#*}" "${a#-*}" "${a#*-*}" bracket "${a#2}" "${s#'*'}" "${h#'#'}" __IN__ [-2-3-4][-2-3-4][-2-3-4][2-3-4] [2-3-4][1-2-3-4][1-2-3-4][2-3-4] [1-2-3-4][**][##] __OUT__ test_oE 'removing longest matching prefix' a=1-2-3-4 s='***' h='###' bracket "${a##1}" "${a##*1}" "${a##1*}" "${a##1*-}" bracket "${a##*-}" "${a##*}" "${a##-*}" "${a##*-*}" bracket "${a##2}" "${s##'*'}" "${h###}" __IN__ [-2-3-4][-2-3-4][][4] [4][][1-2-3-4][] [1-2-3-4][**][##] __OUT__ test_oE 'removing shortest matching suffix' a=1-2-3-4 s='***' p='%%%' bracket "${a%4}" "${a%*4}" "${a%4*}" "${a%-*4}" bracket "${a%*-}" "${a%*}" "${a%-*}" "${a%*-*}" bracket "${a%3}" "${s%'*'}" "${p%'%'}" __IN__ [1-2-3-][1-2-3-][1-2-3-][1-2-3] [1-2-3-4][1-2-3-4][1-2-3][1-2-3] [1-2-3-4][**][%%] __OUT__ test_oE 'removing longest matching suffix' a=1-2-3-4 s='***' p='%%%' bracket "${a%%4}" "${a%%*4}" "${a%%4*}" "${a%%-*4}" bracket "${a%%*-}" "${a%%*}" "${a%%-*}" "${a%%*-*}" bracket "${a%%3}" "${s%%'*'}" "${p%%%}" __IN__ [1-2-3-][][1-2-3-][1] [1-2-3-4][][1][] [1-2-3-4][**][%%] __OUT__ ### Examples from informative sections of POSIX test_oE 'effects of omitting braces' a=1 set 2 echo ${a}b-$ab-${1}0-${10}-$10 __IN__ 1b--20--20 __OUT__ test_oE 'testing existence of positional parameter' set a b c echo ${3:+posix} echo ${4-posix} __IN__ posix posix __OUT__ test_oE 'removing prefix with expanded word' HOME=/home/foo x=$HOME/src/cmd echo ${x#$HOME} __IN__ /src/cmd __OUT__ test_oE 'special parameter #' echo $# set x echo $# set x 'y y' z echo $# set a b c d e f g h i j k echo $# __IN__ 0 1 3 11 __OUT__ test_oE 'special parameter ?' true echo $? (exit 1) echo $? (exit 123) echo $? __IN__ 0 1 123 __OUT__ test_OE -e 0 'special parameter -' -eu set +C v=$- [ "$(echo $v | grep e | grep u | grep -v C)" ] __IN__ test_OE 'special parameter $' [ $$ -eq "$(echo $$)" ] || echo [ $$ -eq "$(echo $$)" ] kill $$ echo not reached __IN__ test_oE 'special parameter !' mkfifo fifo echo foo | { trap 'echo trapped; exit 0' USR1 cat cat fifo }& exec 3>fifo echo bar >&3 kill -s USR1 $! # should kill the last process of the background pipeline exec 3>&- wait $! __IN__ foo bar trapped __OUT__ # Special parameter 0 is tested in sh-p.tst #test_oE 'special parameter 0' test_oE 'special parameter *, quoted, unset IFS' unset IFS bracket "$*" set a bracket "$*" set a 'b b' cc bracket "$*" set '' bracket "$*" set '' '' bracket "$*" __IN__ [] [a] [a b b cc] [] [ ] __OUT__ test_oE 'special parameter *, quoted, non-default IFS' IFS=xyz bracket "$*" set a bracket "$*" set a 'b b' cc bracket "$*" set '' bracket "$*" set '' '' bracket "$*" __IN__ [] [a] [axb bxcc] [] [x] __OUT__ test_oE 'special parameter *, quoted, empty IFS' IFS= bracket "$*" set a bracket "$*" set a 'b b' cc bracket "$*" set '' bracket "$*" set '' '' bracket "$*" __IN__ [] [a] [ab bcc] [] [] __OUT__ test_oE 'special parameter *, unquoted' bracket $* bracket ""$* bracket $*"" set a bracket $* bracket ""$* bracket $*"" set a 'b b' cc bracket $* bracket ""$* bracket $*"" IFS= bracket $* bracket ""$* bracket $*"" __IN__ [] [] [a] [a] [a] [a][b][b][cc] [a][b][b][cc] [a][b][b][cc] [a][b b][cc] [a][b b][cc] [a][b b][cc] __OUT__ test_oE 'special parameter @, quoted' bracket "$@" bracket "=$@=" null= bracket "$null""$@" bracket "$@""$null" bracket "$null""$@""$null" set a bracket "$@" bracket "=$@=" set a 'b b' cc bracket "$@" bracket "=$@=" bracket "$@$@" set '' bracket "$@" set '' '' bracket "$@" bracket "=$@=" bracket "$@$@" __IN__ [==] [] [] [] [a] [=a=] [a][b b][cc] [=a][b b][cc=] [a][b b][cca][b b][cc] [] [][] [=][=] [][][] __OUT__ # Expansion of unquoted $@ is the same as that of unquoted $* test_oE 'special parameter @, unquoted' bracket $@ bracket ""$@ bracket $@"" set a bracket $@ bracket ""$@ bracket $@"" set a 'b b' cc bracket $@ bracket ""$@ bracket $@"" IFS= bracket $@ bracket ""$@ bracket $@"" __IN__ [] [] [a] [a] [a] [a][b][b][cc] [a][b][b][cc] [a][b][b][cc] [a][b b][cc] [a][b b][cc] [a][b b][cc] __OUT__ test_oE '${1+"$@"}' bracket ${1+"$@"} set a bracket ${1+"$@"} set a 'b b' cc bracket ${1+"$@"} set '' bracket ${1+"$@"} set '' '' bracket ${1+"$@"} __IN__ [a] [a][b b][cc] [] [][] __OUT__ test_oE '${foo:-"$@"}' set a 'b b' cc bracket ${foo:-"$@"} foo= bracket ${foo:-"$@"} foo=bar bracket ${foo:-"$@"} __IN__ [a][b b][cc] [a][b b][cc] [bar] __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/param-y.tst000066400000000000000000000307251354143602500156720ustar00rootroot00000000000000# param-y.tst: yash-specific test of parameter expansion setup -d test_oE '${#*}' set 1 22 '3 3' bracket ${#*} bracket "${#*}" __IN__ [1][2][4] [9] __OUT__ test_oE '${#@}' set 1 22 '3 3' bracket ${#@} bracket "${#@}" __IN__ [1][2][4] [1][2][4] __OUT__ test_oE '$ followed by non-special character' bracket $% $+ $, $. $/ $: $= $[ $] $^ $} $~ __IN__ [$%][$+][$,][$.][$/][$:][$=][$[][$]][$^][$}][$~] __OUT__ test_oE '$ followed by space' bracket $ $ $ __IN__ [$][$][$] __OUT__ test_oE "\$'" bracket $'x' __IN__ [$x] __OUT__ test_oE '$"' bracket $"x" __IN__ [$x] __OUT__ test_oE '$&' bracket $&& echo x __IN__ [$] x __OUT__ test_oE '$)' (bracket $) __IN__ [$] __OUT__ test_oE '$;' bracket $; __IN__ [$] __OUT__ test_oE '$<' bracket $' bracket $>&1 __IN__ [$] __OUT__ test_oE '$\' bracket $\x __IN__ [$x] __OUT__ test_oE '$`' bracket $`echo x` __IN__ [$x] __OUT__ test_oE '$|' bracket $| cat __IN__ [$] __OUT__ test_Oe -e 2 '${}' bracket ${} __IN__ syntax error: the parameter name is missing or invalid __ERR__ test_Oe -e 2 '${}}' bracket ${}} __IN__ syntax error: the parameter name is missing or invalid __ERR__ test_Oe -e 2 '${&}' bracket ${&} __IN__ syntax error: the parameter name is missing or invalid __ERR__ test_Oe -e 2 'missing index' bracket ${foo[]} __IN__ syntax error: the index is missing __ERR__ test_Oe -e 2 'missing start index' bracket ${foo[,2]} __IN__ syntax error: the index is missing __ERR__ test_Oe -e 2 'missing end index' bracket ${foo[1,]} __IN__ syntax error: the index is missing __ERR__ test_Oe -e 2 'missing index and closing bracket' bracket ${foo[} __IN__ syntax error: `]' is missing syntax error: `}' is missing __ERR__ #' #` #' #` #] #} test_Oe -e 2 'missing end index and closing bracket' bracket ${foo[1} __IN__ syntax error: `]' is missing syntax error: `}' is missing __ERR__ #' #` #' #` #] #} test_Oe -e 2 'missing closing bracket' bracket ${foo[1,2} __IN__ syntax error: `]' is missing syntax error: `}' is missing __ERR__ #' #` #' #` #] #} test_Oe -e 2 'missing closing brace' bracket ${foo __IN__ syntax error: `}' is missing __ERR__ #' #` #} test_Oe -e 2 'unexpected colon in parameter expansion' bracket ${foo:} __IN__ syntax error: invalid use of `:' in parameter expansion __ERR__ #' #` test_Oe -e 2 'unexpected colon in unclosed parameter expansion' bracket ${foo: __IN__ syntax error: invalid use of `:' in parameter expansion syntax error: `}' is missing __ERR__ #' #} #` #' #` test_Oe -e 2 'colon followed by hash in parameter expansion' bracket ${foo:#bar} __IN__ syntax error: invalid use of `:' in parameter expansion __ERR__ #' #` test_Oe -e 2 'colon followed by percent in parameter expansion' bracket ${foo:%bar} __IN__ syntax error: invalid use of `:' in parameter expansion __ERR__ #' #` test_Oe -e 2 'unexpected exclamation in unclosed parameter expansion' bracket ${foo! __IN__ syntax error: invalid character `!' in parameter expansion __ERR__ #' #` #} test_Oe -e 2 'missing closing brace after hash' bracket ${foo#bar __IN__ syntax error: `}' is missing __ERR__ #' #` #} test_Oe -e 2 'missing closing brace after percent' bracket ${foo%bar __IN__ syntax error: `}' is missing __ERR__ #' #` #} test_Oe -e 2 'missing closing brace after one slash' bracket ${foo/bar __IN__ syntax error: `}' is missing __ERR__ #' #` #} test_Oe -e 2 'missing closing brace after two slashes' bracket ${foo/bar/baz __IN__ syntax error: `}' is missing __ERR__ #' #` #} test_Oe -e 2 '${#foo-bar}' bracket ${#foo-bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo+bar}' bracket ${#foo+bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo?bar}' bracket ${#foo?bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo=bar}' bracket ${#foo=bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo#bar}' bracket ${#foo#bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo%bar}' bracket ${#foo%bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo/bar}' bracket ${#foo/bar} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${#foo/bar/baz}' bracket ${#foo/bar/baz} __IN__ syntax error: invalid use of `#' in parameter expansion __ERR__ #' #` test_oE '${#+}' bracket "${#+}" # substitute empty string if # is set __IN__ [] __OUT__ test_oE '${#=}' bracket ${#=} # assign and substitute empty string if # is unset __IN__ [0] __OUT__ test_oE '${##}' bracket ${##} # length of $# __IN__ [1] __OUT__ test_oE '${##x}' bracket "${##0}" # actually unambiguous, but POSIXly unspecified behavior __IN__ [] __OUT__ test_oE '${#%x}' bracket "${#%0}" # actually unambiguous, but POSIXly unspecified behavior __IN__ [] __OUT__ test_oE '${#/...}' bracket "${#/x/y}" "${#:/x/y}" "${#//x/y}" __IN__ [0][0][0] __OUT__ test_oE '${*#x}' # The matching prefix is removed from each positional parameter and then all # the parameters are concatenated. set '1-1-1' 2 '3 - 3' bracket "${*#*-}" __IN__ [1-1 2 3] __OUT__ test_oE '${*%x}' # The matching suffix is removed from each positional parameter and then all # the parameters are concatenated. set '1-1-1' 2 '3 - 3' bracket "${*%-*}" __IN__ [1-1 2 3 ] __OUT__ test_oE '${@#x}' # The matching suffix is removed from each positional parameter. set '1-1-1' 2 '3 - 3' bracket "${@#*-}" __IN__ [1-1][2][ 3] __OUT__ test_oE '${@%x}' # The matching suffix is removed from each positional parameter. set '1-1-1' 2 '3 - 3' bracket "${@%-*}" __IN__ [1-1][2][3 ] __OUT__ test_oE 'nested expansion' a='..x..' bracket ${{a#..}%..} ${${a#..}%..} bracket ${$(echo 123)%3} ${`echo 123`%3} ${$((3*4))%2} __IN__ [x][x] [12][12][1] __OUT__ test_oE 'disambiguation of ${$...' # None of below are nested expansions bracket ${$++} ${$:++} [ "${$--}" = "$$" ] || echoraw - [ "${$--}" = "$$" ] [ "${$==}" = "$$" ] || echoraw = [ "${$==}" = "$$" ] [ "${$??}" = "$$" ] || echoraw ? [ "${$??}" = "$$" ] [ "${$:--}" = "$$" ] || echoraw :- [ "${$:--}" = "$$" ] [ "${$:==}" = "$$" ] || echoraw := [ "${$:==}" = "$$" ] [ "${$:??}" = "$$" ] || echoraw :? [ "${$:??}" = "$$" ] [ "${$#\#}" = "$$" ] || echoraw \# [ "${$#\#}" = "$$" ] [ "${$%\%}" = "$$" ] || echoraw \% [ "${$%\%}" = "$$" ] [ "${$###}" = "$$" ] || echoraw \## [ "${$###}" = "$$" ] [ "${$%%%}" = "$$" ] || echoraw \%% [ "${$%%%}" = "$$" ] [ "${$/x/y}" = "$$" ] || echoraw / [ "${$/x/y}" = "$$" ] [ "${$:/x/y}" = "$$" ] || echoraw :/ [ "${$:/x/y}" = "$$" ] [ "${$//x/y}" = "$$" ] || echoraw // [ "${$//x/y}" = "$$" ] __IN__ [+][+] __OUT__ test_oE '${a/b/c}' a='123/456/789' bracket ${a/4*6/x} ${a/\//y} ${a/\//} ${a/\/} bracket ${a/#*3/x} ${a/#456/y} bracket ${a/%7*/x} ${a/%456/y} bracket ${a//4*6/x} ${a//\//y} ${a//\//} ${a//\/} bracket ${a:/1*9/x} ${a:/2*9/x} ${a:/1*8/x} __IN__ [123/x/789][123y456/789][123456/789][123456/789] [x/456/789][123/456/789] [123/456/x][123/456/789] [123/x/789][123y456y789][123456789][123456789] [x][123/456/789][123/456/789] __OUT__ test_oE 'scalar parameter index' a='1-2-3' bracket @ "${a[@]}" bracket \* "${a[*]}" bracket \# "${a[#]}" for i in -6 -5 -2 -1 0 1 2 5 6; do bracket $i "${a[i]}" done for i in -6 -5 -1 0 1 5 6; do for j in -6 -5 -1 0 1 5 6; do bracket $i,$j "${a[i,j]}" done done __IN__ [@][1-2-3] [*][1-2-3] [#][5] [-6][] [-5][1] [-2][-] [-1][3] [0][] [1][1] [2][-] [5][3] [6][] [-6,-6][] [-6,-5][1] [-6,-1][1-2-3] [-6,0][] [-6,1][1] [-6,5][1-2-3] [-6,6][1-2-3] [-5,-6][] [-5,-5][1] [-5,-1][1-2-3] [-5,0][] [-5,1][1] [-5,5][1-2-3] [-5,6][1-2-3] [-1,-6][] [-1,-5][] [-1,-1][3] [-1,0][] [-1,1][] [-1,5][3] [-1,6][3] [0,-6][] [0,-5][] [0,-1][] [0,0][] [0,1][] [0,5][] [0,6][] [1,-6][] [1,-5][1] [1,-1][1-2-3] [1,0][] [1,1][1] [1,5][1-2-3] [1,6][1-2-3] [5,-6][] [5,-5][] [5,-1][3] [5,0][] [5,1][] [5,5][3] [5,6][3] [6,-6][] [6,-5][] [6,-1][] [6,0][] [6,1][] [6,5][] [6,6][] __OUT__ test_oE 'array variable index' a=(1 22 '3 3' 4" "4 '') bracket @ "${a[@]}" bracket \* "${a[*]}" bracket \# "${a[#]}" for i in -6 -5 -2 -1 0 1 2 5 6; do bracket $i "${a[i]}" done for i in -6 -5 -1 0 1 5 6; do for j in -6 -5 -1 0 1 5 6; do bracket $i,$j "${a[i,j]}" done done __IN__ [@][1][22][3 3][4 4][] [*][1 22 3 3 4 4 ] [#][5] [-6] [-5][1] [-2][4 4] [-1][] [0] [1][1] [2][22] [5][] [6] [-6,-6] [-6,-5][1] [-6,-1][1][22][3 3][4 4][] [-6,0] [-6,1][1] [-6,5][1][22][3 3][4 4][] [-6,6][1][22][3 3][4 4][] [-5,-6] [-5,-5][1] [-5,-1][1][22][3 3][4 4][] [-5,0] [-5,1][1] [-5,5][1][22][3 3][4 4][] [-5,6][1][22][3 3][4 4][] [-1,-6] [-1,-5] [-1,-1][] [-1,0] [-1,1] [-1,5][] [-1,6][] [0,-6] [0,-5] [0,-1] [0,0] [0,1] [0,5] [0,6] [1,-6] [1,-5][1] [1,-1][1][22][3 3][4 4][] [1,0] [1,1][1] [1,5][1][22][3 3][4 4][] [1,6][1][22][3 3][4 4][] [5,-6] [5,-5] [5,-1][] [5,0] [5,1] [5,5][] [5,6][] [6,-6] [6,-5] [6,-1] [6,0] [6,1] [6,5] [6,6] __OUT__ test_oE 'positional parameter index (*)' set 1 22 '3 3' 4" "4 '' bracket @ "${*[@]}" bracket \* "${*[*]}" bracket \# "${*[#]}" for i in -6 -5 -2 -1 0 1 2 5 6; do bracket $i "${*[i]}" done for i in -6 -5 -1 0 1 5 6; do for j in -6 -5 -1 0 1 5 6; do bracket $i,$j "${*[i,j]}" done done __IN__ [@][1 22 3 3 4 4 ] [*][1 22 3 3 4 4 ] [#][5] [-6][] [-5][1] [-2][4 4] [-1][] [0][] [1][1] [2][22] [5][] [6][] [-6,-6][] [-6,-5][1] [-6,-1][1 22 3 3 4 4 ] [-6,0][] [-6,1][1] [-6,5][1 22 3 3 4 4 ] [-6,6][1 22 3 3 4 4 ] [-5,-6][] [-5,-5][1] [-5,-1][1 22 3 3 4 4 ] [-5,0][] [-5,1][1] [-5,5][1 22 3 3 4 4 ] [-5,6][1 22 3 3 4 4 ] [-1,-6][] [-1,-5][] [-1,-1][] [-1,0][] [-1,1][] [-1,5][] [-1,6][] [0,-6][] [0,-5][] [0,-1][] [0,0][] [0,1][] [0,5][] [0,6][] [1,-6][] [1,-5][1] [1,-1][1 22 3 3 4 4 ] [1,0][] [1,1][1] [1,5][1 22 3 3 4 4 ] [1,6][1 22 3 3 4 4 ] [5,-6][] [5,-5][] [5,-1][] [5,0][] [5,1][] [5,5][] [5,6][] [6,-6][] [6,-5][] [6,-1][] [6,0][] [6,1][] [6,5][] [6,6][] __OUT__ test_oE 'positional parameter index (@)' set 1 22 '3 3' 4" "4 '' bracket @ "${@[@]}" bracket \* "${@[*]}" bracket \# "${@[#]}" for i in -6 -5 -2 -1 0 1 2 5 6; do bracket $i "${@[i]}" done for i in -6 -5 -1 0 1 5 6; do for j in -6 -5 -1 0 1 5 6; do bracket $i,$j "${@[i,j]}" done done __IN__ [@][1][22][3 3][4 4][] [*][1 22 3 3 4 4 ] [#][5] [-6] [-5][1] [-2][4 4] [-1][] [0] [1][1] [2][22] [5][] [6] [-6,-6] [-6,-5][1] [-6,-1][1][22][3 3][4 4][] [-6,0] [-6,1][1] [-6,5][1][22][3 3][4 4][] [-6,6][1][22][3 3][4 4][] [-5,-6] [-5,-5][1] [-5,-1][1][22][3 3][4 4][] [-5,0] [-5,1][1] [-5,5][1][22][3 3][4 4][] [-5,6][1][22][3 3][4 4][] [-1,-6] [-1,-5] [-1,-1][] [-1,0] [-1,1] [-1,5][] [-1,6][] [0,-6] [0,-5] [0,-1] [0,0] [0,1] [0,5] [0,6] [1,-6] [1,-5][1] [1,-1][1][22][3 3][4 4][] [1,0] [1,1][1] [1,5][1][22][3 3][4 4][] [1,6][1][22][3 3][4 4][] [5,-6] [5,-5] [5,-1][] [5,0] [5,1] [5,5][] [5,6][] [6,-6] [6,-5] [6,-1] [6,0] [6,1] [6,5] [6,6] __OUT__ test_oE 'arithmetics in parameter index' a=12345 b='* 2' echo ${a[1 + 1, (1 + 1) $b]} __IN__ 234 __OUT__ test_oE 'backslash in parameter index' a=abcde echo ${a[1\+1,2\*2]} echo ${a[\@]} __IN__ bcd abcde __OUT__ test_oE 'valid assignment to array element in expansion' a=('' '' '') bracket "${a[1]=x}" "${a[2]=y}" "${a[3]=z}" bracket ${a[1]:=1} bracket "${a}" __IN__ [][][] [1] [1][][] __OUT__ test_oE 'ignored assignment to array element in expansion' a=(1) bracket ${a[1]:=x} bracket "${a}" __IN__ [1] [1] __OUT__ test_Oe -e 2 'out-of-range assignment to array element in expansion' a=(1) eval 'bracket ${a[2]:=x}' __IN__ eval: index 2 is out of range (the actual size of array $a is 1) __ERR__ test_Oe 'assignment to read-only array element in expansion' a=('') readonly a eval 'bracket "${a[1]:=X}"' __IN__ eval: $a is read-only __ERR__ test_oE 'exportation of array' a=() b=(1) c=(1 2) d=(1 ': :' 3) export a b c d sh -c 'printf "[%s]\n" "$a" "$b" "$c" "$d"' __IN__ [] [1] [1:2] [1:: ::3] __OUT__ ( posix="true" test_Oe -e 2 'nested expansion (with $) unavailable in POSIX mode' echo ${${a}} __IN__ syntax error: invalid character `{' in parameter expansion __ERR__ #' #` test_Oe -e 2 'nested expansion (w/o $) unavailable in POSIX mode' echo ${{a}} __IN__ syntax error: the parameter name is missing or invalid __ERR__ test_Oe -e 2 'index unavailable in POSIX mode' a=123 echo ${a[1]} __IN__ syntax error: invalid character `[' in parameter expansion __ERR__ #' #` test_Oe -e 2 '${foo/bar} unavailable in POSIX mode' a=123 echo ${a/2} __IN__ syntax error: invalid character `/' in parameter expansion __ERR__ #' #` ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/path-p.tst000066400000000000000000000032541354143602500155120ustar00rootroot00000000000000# path-p.tst: test of pathname expansion for any POSIX-compliant shell posix="true" mkdir -p foo/dir foo/no_read_dir foo/no_search_dir >foo/dir/file >foo/no_read_dir/file >foo/no_search_dir/file chmod a-r foo/no_read_dir chmod a-x foo/no_search_dir mkdir -p "bar/a[b/c]d" mkdir -p baz/.dir/.file test_oE 'expansion with read-and-searchable directory' echo foo/*dir echo foo/d*r/f*e __IN__ foo/dir foo/no_read_dir foo/no_search_dir foo/dir/file __OUT__ test_oE '* does not match slash' echo foo*dir*file __IN__ foo*dir*file __OUT__ test_oE '? does not match slash' echo foo?dir?file __IN__ foo?dir?file __OUT__ test_oE '[...] does not match slash' echo foo[/]dir[/]file echo bar/a[b/c]d __IN__ foo[/]dir[/]file bar/a[b/c]d __OUT__ test_oE '* does not match initial dot' echo baz/*dir/*file __IN__ baz/*dir/*file __OUT__ test_oE '? does not match initial dot' echo baz/?dir/?file __IN__ baz/?dir/?file __OUT__ test_oE '[!...] does not match initial dot' echo baz/[!a]dir/[!1-9]file __IN__ baz/[!a]dir/[!1-9]file __OUT__ ( # Skip if we're root. if { ls for/dir || /dev/null; then skip="true" fi test_oE 'expansion with unreadable directory' echo foo/no_read_d*r/file echo foo/no_read_dir/f*e echo foo/no_read_d*r/f*e __IN__ foo/no_read_dir/file foo/no_read_dir/f*e foo/no_read_d*r/f*e __OUT__ test_oE 'expansion with unsearchable directory' echo foo/no_search_d*r/file echo foo/no_search_dir/f*e echo foo/no_search_d*r/f*e __IN__ foo/no_search_d*r/file foo/no_search_dir/file foo/no_search_dir/file __OUT__ ) test_oE 'pathnames are sorted according to current collating sequence' echo [fb]*/ __IN__ bar/ baz/ foo/ __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/pipeline-p.tst000066400000000000000000000021531354143602500163600ustar00rootroot00000000000000# pipeline-p.tst: test of pipeline for any POSIX-compliant shell posix="true" test_o '2-command pipeline' echo foo | cat __IN__ foo __OUT__ test_o '3-command pipeline' printf '%s\n' foo bar | tail -n 1 | cat __IN__ bar __OUT__ test_o 'linebreak after |' printf '%s\n' foo bar | tail -n 1 | cat __IN__ bar __OUT__ test_OE -e 3 'exit status of pipeline is from last command' exit 1 | exit 2 | exit 3 __IN__ test_oE 'exit status of negated pipelines' exit 1 | exit 2 | exit 3 echo a $? ! exit 1 | exit 2 | exit 3 echo b $? ! exit 1 | exit 2 | exit 1 echo c $? ! exit 1 | exit 2 | exit 0 echo d $? __IN__ a 3 b 0 c 0 d 1 __OUT__ test_oE 'stdin for first command & stdout for last are not modified' cat | tail -n 1 foo bar __IN__ bar __OUT__ test_Oe 'stderr is not modified' (echo >&2) | (echo >&2) __IN__ __ERR__ test_oE 'compound commands in pipeline' { echo foo echo bar } | ( tail -n 1 echo baz ) | if true; then cat else : fi __IN__ bar baz __OUT__ test_OE 'redirection overrides pipeline' echo foo >/dev/null | cat echo foo | main.out (echo $PPID) >subshell.out diff main.out subshell.out __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/printf-y.tst000066400000000000000000000316511354143602500160730ustar00rootroot00000000000000# printf-y.tst: yash-specific test of the printf built-in test_OE 'empty operand' printf '' __IN__ test_oE 'newline' printf '\n' __IN__ __OUT__ if ! testee -c 'command -bv printf' >/dev/null; then skip="true" fi test_oE 'normal characters' printf \''ABC 123 !"$#&()-=^~|@`[]{}<>;,:.+*/?_'\' echo __IN__ 'ABC 123 !"$#&()-=^~|@`[]{}<>;,:.+*/?_' __OUT__ test_oE 'unformatted operands are ignored' printf 'ABC\n' ignored operands __IN__ ABC __OUT__ test_oE 'newline 2' printf 'Multiple\nlines ' printf 'continued\n' __IN__ Multiple lines continued __OUT__ test_oE 'escaped backslash' printf '[\\]\n' __IN__ [\] __OUT__ test_oE 'escaped double-quote' printf '[\"]\n' __IN__ ["] __OUT__ test_oE 'escaped single-quote' printf "[\\']\n" __IN__ ['] __OUT__ test_oE 'various escapes' printf 'a\ab\bf\fr\rt\tv\v-\n' __IN__ abf r t v - __OUT__ test_oE 'octal numbers' printf 'octal \11 \1000\n' __IN__ octal @0 __OUT__ test_oE '%d' printf '%d\n' printf '%d\n' 0 printf '%d ' 0 10 200 +345 -1278; echo 1 printf '%+d ' 1 20 +345 -1278; echo 2 printf '%+ d ' 1 20 +345 -1278; echo 3 printf '% d ' 1 20 +345 -1278; echo 4 printf '%+8d ' 1 20 +345 -1278; echo 5 printf '%+-8d ' 1 20 +345 -1278; echo 6 printf '%+08d ' 1 20 +345 -1278; echo 7 printf '%+-08d ' 1 20 +345 -1278; echo 8 printf '%+8.3d ' 1 20 +345 -1278; echo 9 printf '%d ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 __IN__ 0 0 0 10 200 345 -1278 1 +1 +20 +345 -1278 2 +1 +20 +345 -1278 3 1 20 345 -1278 4 +1 +20 +345 -1278 5 +1 +20 +345 -1278 6 +0000001 +0000020 +0000345 -0001278 7 +1 +20 +345 -1278 8 +001 +020 +345 -1278 9 0 9 510 510 97 64 10 __OUT__ test_oE '%i' printf '%i\n' printf '%i\n' 0 printf '%i ' 0 10 200 +345 -1278; echo 1 printf '%+i ' 1 20 +345 -1278; echo 2 printf '%+ i ' 1 20 +345 -1278; echo 3 printf '% i ' 1 20 +345 -1278; echo 4 printf '%+8i ' 1 20 +345 -1278; echo 5 printf '%+-8i ' 1 20 +345 -1278; echo 6 printf '%+08i ' 1 20 +345 -1278; echo 7 printf '%+-08i ' 1 20 +345 -1278; echo 8 printf '%+8.3i ' 1 20 +345 -1278; echo 9 printf '%i ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 __IN__ 0 0 0 10 200 345 -1278 1 +1 +20 +345 -1278 2 +1 +20 +345 -1278 3 1 20 345 -1278 4 +1 +20 +345 -1278 5 +1 +20 +345 -1278 6 +0000001 +0000020 +0000345 -0001278 7 +1 +20 +345 -1278 8 +001 +020 +345 -1278 9 0 9 510 510 97 64 10 __OUT__ test_oE '%u' printf '%u\n' printf '%u\n' 0 printf '%u ' 0 10 200 +345; echo 1 printf '%+u ' 1 20 +345; echo 2 printf '%+ u ' 1 20 +345; echo 3 printf '% u ' 1 20 +345; echo 4 printf '%8u ' 1 20 +345; echo 5 printf '%-8u ' 1 20 +345; echo 6 printf '%08u ' 1 20 +345; echo 7 printf '%-08u ' 1 20 +345; echo 8 printf '%8.3u ' 1 20 +345; echo 9 printf '%u ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 __IN__ 0 0 0 10 200 345 1 1 20 345 2 1 20 345 3 1 20 345 4 1 20 345 5 1 20 345 6 00000001 00000020 00000345 7 1 20 345 8 001 020 345 9 0 9 510 510 97 64 10 __OUT__ test_oE '%o' printf '%o\n' printf '%o\n' 0 printf '%o ' 0 10 200 +345; echo 1 printf '%+o ' 1 20 +345; echo 2 printf '%+ o ' 1 20 +345; echo 3 printf '% o ' 1 20 +345; echo 4 printf '%8o ' 1 20 +345; echo 5 printf '%-8o ' 1 20 +345; echo 6 printf '%08o ' 1 20 +345; echo 7 printf '%-08o ' 1 20 +345; echo 8 printf '%8.3o ' 1 20 +345; echo 9 printf '%o ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 printf '%#.3o ' 0 3 010 0123 012345; echo 11 __IN__ 0 0 0 12 310 531 1 1 24 531 2 1 24 531 3 1 24 531 4 1 24 531 5 1 24 531 6 00000001 00000024 00000531 7 1 24 531 8 001 024 531 9 0 11 776 776 141 100 10 000 003 010 0123 012345 11 __OUT__ test_oE '%x' printf '%x\n' printf '%x\n' 0 printf '%x ' 0 10 200 +345; echo 1 printf '%+x ' 1 20 +345; echo 2 printf '%+ x ' 1 20 +345; echo 3 printf '% x ' 1 20 +345; echo 4 printf '%8x ' 1 20 +345; echo 5 printf '%-8x ' 1 20 +345; echo 6 printf '%08x ' 1 20 +345; echo 7 printf '%-08x ' 1 20 +345; echo 8 printf '%8.3x ' 1 20 +345; echo 9 printf '%x ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 printf '%#x ' 00 011 0x1fE 0X1Fe \'a \"@; echo 11 __IN__ 0 0 0 a c8 159 1 1 14 159 2 1 14 159 3 1 14 159 4 1 14 159 5 1 14 159 6 00000001 00000014 00000159 7 1 14 159 8 001 014 159 9 0 9 1fe 1fe 61 40 10 0 0x9 0x1fe 0x1fe 0x61 0x40 11 __OUT__ test_oE '%X' printf '%X\n' printf '%X\n' 0 printf '%X ' 0 10 200 +345; echo 1 printf '%+X ' 1 20 +345; echo 2 printf '%+ X ' 1 20 +345; echo 3 printf '% X ' 1 20 +345; echo 4 printf '%8X ' 1 20 +345; echo 5 printf '%-8X ' 1 20 +345; echo 6 printf '%08X ' 1 20 +345; echo 7 printf '%-08X ' 1 20 +345; echo 8 printf '%8.3X ' 1 20 +345; echo 9 printf '%X ' 00 011 0x1fE 0X1Fe \'a \"@; echo 10 printf '%#X ' 00 011 0x1fE 0X1Fe \'a \"@; echo 11 __IN__ 0 0 0 A C8 159 1 1 14 159 2 1 14 159 3 1 14 159 4 1 14 159 5 1 14 159 6 00000001 00000014 00000159 7 1 14 159 8 001 014 159 9 0 9 1FE 1FE 61 40 10 0 0X9 0X1FE 0X1FE 0X61 0X40 11 __OUT__ test_oE '%f' printf '%f\n' printf '%f\n' 0 printf '%f ' 1 1.25 -1000.0; echo 1 printf '%+f ' 1 1.25 -1000.0; echo 2 printf '%+ f ' 1 1.25 -1000.0; echo 3 printf '% f ' 1 1.25 -1000.0; echo 4 printf '%+15f ' 1 1.25 -1000.0; echo 5 printf '%+-15f ' 1 1.25 -1000.0; echo 6 printf '%+015f ' 1 1.25 -1000.0; echo 7 printf '%+-015f ' 1 1.25 -1000.0; echo 8 printf '%+15.3f ' 1 1.25 -1000.0; echo 9 printf '%.0f ' 1 1.25 -1000.0; echo 10 printf '%#.0f ' 1 1.25 -1000.0; echo 11 __IN__ 0.000000 0.000000 1.000000 1.250000 -1000.000000 1 +1.000000 +1.250000 -1000.000000 2 +1.000000 +1.250000 -1000.000000 3 1.000000 1.250000 -1000.000000 4 +1.000000 +1.250000 -1000.000000 5 +1.000000 +1.250000 -1000.000000 6 +0000001.000000 +0000001.250000 -0001000.000000 7 +1.000000 +1.250000 -1000.000000 8 +1.000 +1.250 -1000.000 9 1 1 -1000 10 1. 1. -1000. 11 __OUT__ test_oE '%F' printf '%F\n' printf '%F\n' 0 printf '%F ' 1 1.25 -1000.0; echo 1 printf '%+F ' 1 1.25 -1000.0; echo 2 printf '%+ F ' 1 1.25 -1000.0; echo 3 printf '% F ' 1 1.25 -1000.0; echo 4 printf '%+15F ' 1 1.25 -1000.0; echo 5 printf '%+-15F ' 1 1.25 -1000.0; echo 6 printf '%+015F ' 1 1.25 -1000.0; echo 7 printf '%+-015F ' 1 1.25 -1000.0; echo 8 printf '%+15.3F ' 1 1.25 -1000.0; echo 9 printf '%.0F ' 1 1.25 -1000.0; echo 10 printf '%#.0F ' 1 1.25 -1000.0; echo 11 __IN__ 0.000000 0.000000 1.000000 1.250000 -1000.000000 1 +1.000000 +1.250000 -1000.000000 2 +1.000000 +1.250000 -1000.000000 3 1.000000 1.250000 -1000.000000 4 +1.000000 +1.250000 -1000.000000 5 +1.000000 +1.250000 -1000.000000 6 +0000001.000000 +0000001.250000 -0001000.000000 7 +1.000000 +1.250000 -1000.000000 8 +1.000 +1.250 -1000.000 9 1 1 -1000 10 1. 1. -1000. 11 __OUT__ test_oE '%e' printf '%e\n' printf '%e\n' 0 printf '%e ' 1 1.25 -1000.0; echo 1 printf '%+e ' 1 1.25 -1000.0; echo 2 printf '%+ e ' 1 1.25 -1000.0; echo 3 printf '% e ' 1 1.25 -1000.0; echo 4 printf '%+15e ' 1 1.25 -1000.0; echo 5 printf '%+-15e ' 1 1.25 -1000.0; echo 6 printf '%+015e ' 1 1.25 -1000.0; echo 7 printf '%+-015e ' 1 1.25 -1000.0; echo 8 printf '%+15.3e ' 1 1.25 -1000.0; echo 9 printf '%.0e ' 1 1.25 -1000.0; echo 10 printf '%#.0e ' 1 1.25 -1000.0; echo 11 __IN__ 0.000000e+00 0.000000e+00 1.000000e+00 1.250000e+00 -1.000000e+03 1 +1.000000e+00 +1.250000e+00 -1.000000e+03 2 +1.000000e+00 +1.250000e+00 -1.000000e+03 3 1.000000e+00 1.250000e+00 -1.000000e+03 4 +1.000000e+00 +1.250000e+00 -1.000000e+03 5 +1.000000e+00 +1.250000e+00 -1.000000e+03 6 +001.000000e+00 +001.250000e+00 -001.000000e+03 7 +1.000000e+00 +1.250000e+00 -1.000000e+03 8 +1.000e+00 +1.250e+00 -1.000e+03 9 1e+00 1e+00 -1e+03 10 1.e+00 1.e+00 -1.e+03 11 __OUT__ test_oE '%E' printf '%E\n' printf '%E\n' 0 printf '%E ' 1 1.25 -1000.0; echo 1 printf '%+E ' 1 1.25 -1000.0; echo 2 printf '%+ E ' 1 1.25 -1000.0; echo 3 printf '% E ' 1 1.25 -1000.0; echo 4 printf '%+15E ' 1 1.25 -1000.0; echo 5 printf '%+-15E ' 1 1.25 -1000.0; echo 6 printf '%+015E ' 1 1.25 -1000.0; echo 7 printf '%+-015E ' 1 1.25 -1000.0; echo 8 printf '%+15.3E ' 1 1.25 -1000.0; echo 9 printf '%.0E ' 1 1.25 -1000.0; echo 10 printf '%#.0E ' 1 1.25 -1000.0; echo 11 __IN__ 0.000000E+00 0.000000E+00 1.000000E+00 1.250000E+00 -1.000000E+03 1 +1.000000E+00 +1.250000E+00 -1.000000E+03 2 +1.000000E+00 +1.250000E+00 -1.000000E+03 3 1.000000E+00 1.250000E+00 -1.000000E+03 4 +1.000000E+00 +1.250000E+00 -1.000000E+03 5 +1.000000E+00 +1.250000E+00 -1.000000E+03 6 +001.000000E+00 +001.250000E+00 -001.000000E+03 7 +1.000000E+00 +1.250000E+00 -1.000000E+03 8 +1.000E+00 +1.250E+00 -1.000E+03 9 1E+00 1E+00 -1E+03 10 1.E+00 1.E+00 -1.E+03 11 __OUT__ test_oE '%g' printf '%g\n' printf '%g\n' 0 printf '%g ' 1 1.25 -1000.0 0.000025; echo 1 printf '%+g ' 1 1.25 -1000.0 0.000025; echo 2 printf '%+ g ' 1 1.25 -1000.0 0.000025; echo 3 printf '% g ' 1 1.25 -1000.0 0.000025; echo 4 printf '%+15g ' 1 1.25 -1000.0 0.000025; echo 5 printf '%+-15g ' 1 1.25 -1000.0 0.000025; echo 6 printf '%+015g ' 1 1.25 -1000.0 0.000025; echo 7 printf '%+-015g ' 1 1.25 -1000.0 0.000025; echo 8 printf '%+15.3g ' 1 1.25 -1000.0 0.000025; echo 9 printf '%+#15.3g ' 1 1.25 -1000.0 0.000025; echo 10 __IN__ 0 0 1 1.25 -1000 2.5e-05 1 +1 +1.25 -1000 +2.5e-05 2 +1 +1.25 -1000 +2.5e-05 3 1 1.25 -1000 2.5e-05 4 +1 +1.25 -1000 +2.5e-05 5 +1 +1.25 -1000 +2.5e-05 6 +00000000000001 +00000000001.25 -00000000001000 +00000002.5e-05 7 +1 +1.25 -1000 +2.5e-05 8 +1 +1.25 -1e+03 +2.5e-05 9 +1.00 +1.25 -1.00e+03 +2.50e-05 10 __OUT__ test_oE '%G' printf '%G\n' printf '%G\n' 0 printf '%G ' 1 1.25 -1000.0 0.000025; echo 1 printf '%+G ' 1 1.25 -1000.0 0.000025; echo 2 printf '%+ G ' 1 1.25 -1000.0 0.000025; echo 3 printf '% G ' 1 1.25 -1000.0 0.000025; echo 4 printf '%+15G ' 1 1.25 -1000.0 0.000025; echo 5 printf '%+-15G ' 1 1.25 -1000.0 0.000025; echo 6 printf '%+015G ' 1 1.25 -1000.0 0.000025; echo 7 printf '%+-015G ' 1 1.25 -1000.0 0.000025; echo 8 printf '%+15.3G ' 1 1.25 -1000.0 0.000025; echo 9 printf '%+#15.3G ' 1 1.25 -1000.0 0.000025; echo 10 __IN__ 0 0 1 1.25 -1000 2.5E-05 1 +1 +1.25 -1000 +2.5E-05 2 +1 +1.25 -1000 +2.5E-05 3 1 1.25 -1000 2.5E-05 4 +1 +1.25 -1000 +2.5E-05 5 +1 +1.25 -1000 +2.5E-05 6 +00000000000001 +00000000001.25 -00000000001000 +00000002.5E-05 7 +1 +1.25 -1000 +2.5E-05 8 +1 +1.25 -1E+03 +2.5E-05 9 +1.00 +1.25 -1.00E+03 +2.50E-05 10 __OUT__ test_oE '%c' printf '%c\n' printf '%c\n' '' printf '%c\n' a printf '%c\n' long arguments printf '%3c\n' b printf '%-3c\n' c __IN__ a l a b c __OUT__ test_oE '%s' printf '%s\n' printf '%s\n' 'argument with space and newline' printf '%s\n' 'argument with backslash \\ and percent %%' printf '%5s ' 123 a long_argument; echo 1 printf '%5.2s ' 123 a long_argument; echo 2 printf '%-5s ' 123 a long_argument; echo 3 __IN__ argument with space and newline argument with backslash \\ and percent %% 123 a long_argument 1 12 a lo 2 123 a long_argument 3 __OUT__ test_oE '%b' printf '%b\n' printf '%b\n' 'argument with space and newline' printf '%b\n' 'argument with backslash \\ and percent %%' printf '%5b ' 123 a long_argument; echo 1 printf '%5.2b ' 123 a long_argument; echo 2 printf '%-5b ' 123 a long_argument; echo 3 printf '%-5.2b ' 123 a long_argument; echo 4 printf '%b\n' '1\a2\b3\c4' 5 printf '%b\n' '6\f7\n8\r9\t0\v!' printf '%b\n' '\0123\012\01x' '\123\12\1x' '\00411' __IN__ argument with space and newline argument with backslash \ and percent %% 123 a long_argument 1 12 a lo 2 123 a long_argument 3 12 a lo 4 1236 7 8 9 0 ! S x \123\12\1x !1 __OUT__ test_oE 'percent' printf '%%\n' printf '+%%+%%%%+\n' __IN__ % +%+%%+ __OUT__ test_oE 'percent does not consume operand' printf '%d%%%d\n' 1 2 __IN__ 1%2 __OUT__ test_o -d -e n 'operands in invalid format' printf '%d\n' not_a_integer 32_trailing_characters __IN__ 0 32 __OUT__ test_x -d -e n 'operand overflow is an error' printf '%d\n' 999999999999999999999999999999999999999999999999999999999999999 __IN__ test_o 'something is printed even in operand overflow' echo $( printf '%d\n' 999999999999999999999999999999999999999999999999999999999999999 | grep -c . ) __IN__ 1 __OUT__ test_Oe -e n 'invalid option' printf --no-such-option '' __IN__ printf: `--no-such-option' is not a valid option __ERR__ #' #` test_O -d -e n 'printing to closed stream' printf '\n' >&- __IN__ test_Oe -e n 'missing format' printf __IN__ printf: this command requires an operand __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/prompt-y.tst000066400000000000000000000077301354143602500161130ustar00rootroot00000000000000# prompt-y.tst: yash-specific test of input processing user_id="$(id -u)" mkfifo fifo ( if [ "$user_id" -eq 0 ]; then skip="true" fi ( # Detail behavior of prompting and command history is different among shell # implementations, so we don't test it in input-p.tst. posix="true" test_e 'expansion in PS1 (POSIX)' -i +m PS1='ps1 %'; a=A; echo >&2 PS1='${a} @'; echo >&2 PS1=''; echo >&2 __IN__ $ ps1 % A @ __ERR__ test_e 'PS2' -i +m PS2='${b} %'; \ echo >&2 b=B; \ echo >&2 true && echo >&2; exit __IN__ $ > $ % $ B % __ERR__ # It is POSIXly unspecified (1) whether assignment to $PS4 affects the prompt # for the assignment itself, and (2) how characters are quoted in xtrace. This # test case tests yash-specific behavior. test_e 'PS4' -x echo 0 PS4='ps4 ${x}:' x='${y}' echo 1; echo '2 2' 3 __IN__ + echo 0 ps4 ${y}:PS4='ps4 ${x}:' x='${y}' ps4 ${y}:echo 1 ps4 ${y}:echo '2 2' 3 __ERR__ ( if ! "$TESTEE" -c 'command -bv fc history' >/dev/null; then skip="true" fi export HISTFILE=/dev/null HISTRMDUP=1 test_e 'job number expansion in PS1' -i +m PS1='#!$'; echo >&2 echo >&2 echo >&2 echo >&2; exit __IN__ $ #2$ #3$ #3$ __ERR__ test_e 'literal exclamation in PS1' -i +m PS1='!!$'; echo >&2 echo >&2 echo >&2 echo >&2; exit __IN__ $ !$ !$ !$ __ERR__ ) test_e 'YASH_PSx is ignored in POSIX mode' -i +m PS1='NO1' YASH_PS1='YES1' PS2='NO2' YASH_PS2='YES2'; echo >&2 \ echo >&2; exit __IN__ $ NO1NO2 __ERR__ test_Oe 'PROMPT_COMMAND is ignored in POSIX mode' -i +m PROMPT_COMMAND='echo not printed'; echo >&2 echo >&2; exit __IN__ $ $ __ERR__ ) test_e 'YASH_PSx precedes PSx (non-POSIX)' -i +m PS1='NO1' YASH_PS1='YES1' PS2='NO2' YASH_PS2='YES2'; echo >&2 \ echo >&2; exit __IN__ $ YES1YES2 __ERR__ test_e 'expansion and substitution in PS1' -i +m PS1='${PWD##"$PWD"}$(echo \?)'; echo >&2 PS1='! !! $ '; echo >&2 echo >&2; exit __IN__ $ ? ! !! $ __ERR__ # TODO: Test of \[, \], and \f is missing # \j and \$ are tested in other test cases below test_e 'backslash notations in PS1' -i +m PS1='\a \e \n \r $(printf \\\\)\ $'; echo >&2 echo >&2; exit __IN__ $   \ $ __ERR__ # TODO: Test of \j, \[, \], and \f is missing # \$ is tested in another test case below test_e 'backslash notations in PS2' -i +m PS2='\a \e \n \r \\ >'; echo >&2 \ echo >&2; exit __IN__ $ $   \ > __ERR__ test_e '\j in PS1: shows job count' -i +m PS1='\j$'; echo >&2 :& echo >&2 :&&:& echo >&2 wait $!; echo >&2 wait; echo >&2 echo >&2; exit __IN__ $ 0$ 1$ 2$ 1$ 0$ __ERR__ # This test case occasionally fails, perhaps when the shell did not receive the # SIGCHLD signal for the 'exec >fifo' child process before the prompt for the # line containing the wait command. The three sleep commands should mitigate # this, but if the test still fails, please just retry. # See: https://osdn.net/tracker.php?id=37560 test_e '\j in PS1 and -b option' -ib +m PS1='\j$'; echo >&2 exec >fifo& echo >&2 cat fifo; sleep 0; sleep 0; sleep 0 wait $!; echo >&2 echo >&2; exit __IN__ $ 0$[1] + Running exec 1>fifo 1$[1] + Done exec 1>fifo 0$ 0$ __ERR__ test_e 'prompt command' -i +m PROMPT_COMMAND='printf 1 >&2'; echo >&2 PROMPT_COMMAND=('printf 1 >&2' 'printf 2 >&2; printf 3 >&2; (exit 2)'); echo $? >&2; (exit 1) echo $? >&2; exit __IN__ $ 1$ > 0 123$ 1 __ERR__ test_e '\$ in PS1 and PS2 (non-root)' -i +m PS1='\$ ' PS2='\$_'; echo >&2 e\ c\ ho >&2; exit __IN__ $ $ $_$_ __ERR__ ) ( if [ "$user_id" -ne 0 ]; then skip="true" fi test_e '\$ in PS1 and PS2 (root)' -i +m PS1='\$ ' PS2='\$_'; echo >&2 e\ c\ ho >&2; exit __IN__ # # #_#_ __ERR__ ) ( setup -d ( if [ "$user_id" -ne 0 ]; then skip="true" fi test_o 'default prompt strings (root)' -i +m bracket "$PS1" bracket "$PS2" bracket "$PS4" __IN__ [# ] [> ] [+ ] __OUT__ ) ( if [ "$user_id" -eq 0 ]; then skip="true" fi test_o 'default prompt strings (non-root)' -i +m bracket "$PS1" bracket "$PS2" bracket "$PS4" __IN__ [$ ] [> ] [+ ] __OUT__ ) ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/ptwrap.c000066400000000000000000000177241354143602500152550ustar00rootroot00000000000000/* ptwrap.c: a simple tool that runs a command in a pseudo-terminal */ /* MIT License Copyright (c) 2016-2019 WATANABE Yuki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define _XOPEN_SOURCE 600 #define _DARWIN_C_SOURCE 1 #include #include #include #include #include #include #include #include /* Not defined in X/Open */ #include #include #include #include static const char *program_name; static void error_exit(const char *message) { fprintf(stderr, "%s: %s\n", program_name, message); exit(EXIT_FAILURE); } static void errno_exit(const char *message) { fprintf(stderr, "%s: ", program_name); perror(message); exit(EXIT_FAILURE); } static int prepare_master_pseudo_terminal(void) { int fd = posix_openpt(O_RDWR | O_NOCTTY); if (fd < 0) errno_exit("cannot open master pseudo-terminal"); if (fd <= STDERR_FILENO) error_exit("stdin/stdout/stderr are not open"); if (grantpt(fd) < 0) errno_exit("pseudo-terminal permission not granted"); if (unlockpt(fd) < 0) errno_exit("pseudo-terminal permission not unlocked"); return fd; } static const char *slave_pseudo_terminal_name(int master_fd) { errno = 0; /* ptsname may not assign to errno, even if on error */ const char *name = ptsname(master_fd); if (name == NULL) errno_exit("cannot name slave pseudo-terminal"); return name; } static int open_noctty(const char *pathname) { int fd = open(pathname, O_RDWR | O_NOCTTY); if (fd < 0) errno_exit("cannot open slave pseudo-terminal"); return fd; } enum state_T { INACTIVE, READING, WRITING, }; struct channel_T { int from_fd, to_fd; enum state_T state; char buffer[BUFSIZ]; size_t buffer_position, buffer_length; }; static void set_fd_set( struct channel_T *channel, fd_set *read_fds, fd_set *write_fds) { switch (channel->state) { case INACTIVE: break; case READING: FD_SET(channel->from_fd, read_fds); break; case WRITING: FD_SET(channel->to_fd, write_fds); break; } } static void process_buffer( struct channel_T *channel, fd_set *read_fds, fd_set *write_fds) { ssize_t size; switch (channel->state) { case INACTIVE: break; case READING: if (!FD_ISSET(channel->from_fd, read_fds)) break; channel->buffer_position = 0; size = read(channel->from_fd, channel->buffer, BUFSIZ); if (size <= 0) { channel->state = INACTIVE; } else { channel->state = WRITING; channel->buffer_length = size; } break; case WRITING: if (!FD_ISSET(channel->to_fd, write_fds)) break; assert(channel->buffer_position < channel->buffer_length); size = write(channel->to_fd, &channel->buffer[channel->buffer_position], channel->buffer_length - channel->buffer_position); if (size < 0) break; /* ignore any error */ channel->buffer_position += size; if (channel->buffer_position == channel->buffer_length) channel->state = READING; break; } } static void forward_all_io(int master_fd) { struct channel_T outgoing; outgoing.from_fd = master_fd; outgoing.to_fd = STDOUT_FILENO; outgoing.state = READING; /* Loop until all output from the slave is forwarded, so that we don't * miss any output. */ while (outgoing.state != INACTIVE) { /* await next IO */ fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_ZERO(&write_fds); set_fd_set(&outgoing, &read_fds, &write_fds); if (select(master_fd + 1, &read_fds, &write_fds, NULL, NULL) < 0) errno_exit("cannot find file descriptor to forward"); /* read to or write from buffer */ process_buffer(&outgoing, &read_fds, &write_fds); } } static int await_child(pid_t child_pid) { int wait_status; if (waitpid(child_pid, &wait_status, 0) != child_pid) errno_exit("cannot await child process"); if (WIFEXITED(wait_status)) return WEXITSTATUS(wait_status); if (WIFSIGNALED(wait_status)) return WTERMSIG(wait_status) | 0x80; return EXIT_FAILURE; } static void become_session_leader(void) { if (setsid() < 0) errno_exit("cannot create new session"); } static void prepare_slave_pseudo_terminal_fds(const char *slave_name) { /* How to become the controlling process of a slave pseudo-terminal is * implementation-dependent. We support two implementation schemes: * (1) A process automatically becomes the controlling process when it * first opens the terminal. * (2) A process needs to use the TIOCSCTTY ioctl system call. * There is a race condition in both schemes: an unrelated process could * become the controlling process before we do, in which case the slave is * not our controlling terminal and therefore we should abort. */ if (close(STDIN_FILENO) < 0) errno_exit("cannot close old stdin"); int slave_fd = open(slave_name, O_RDWR); if (slave_fd != STDIN_FILENO) errno_exit("cannot open slave pseudo-terminal at stdin"); if (close(STDOUT_FILENO) < 0) errno_exit("cannot close old stdout"); if (dup(slave_fd) != STDOUT_FILENO) errno_exit("cannot open slave pseudo-terminal at stdout"); if (close(STDERR_FILENO) < 0) errno_exit("cannot close old stderr"); if (dup(slave_fd) != STDERR_FILENO) errno_exit("cannot open slave pseudo-terminal at stderr"); #ifdef TIOCSCTTY ioctl(slave_fd, TIOCSCTTY, NULL); #endif /* defined(TIOCSCTTY) */ if (tcgetpgrp(slave_fd) != getpgrp()) error_exit( "cannot become controlling process of slave pseudo-terminal"); } static void exec_command(char *argv[]) { execvp(argv[0], argv); errno_exit(argv[0]); } int main(int argc, char *argv[]) { if (argc <= 0) exit(EXIT_FAILURE); program_name = argv[0]; /* Don't use getopt, because we don't want glibc's reordering extension. if (getopt(argc, argv, "") != -1) exit(EXIT_FAILURE); */ optind = 1; if (optind < argc && strcmp(argv[optind], "--") == 0) optind++; if (optind == argc) error_exit("operand missing"); int master_fd = prepare_master_pseudo_terminal(); const char *slave_name = slave_pseudo_terminal_name(master_fd); int slave_fd = open_noctty(slave_name); pid_t child_pid = fork(); if (child_pid < 0) errno_exit("cannot spawn child process"); if (child_pid > 0) { /* parent process */ close(slave_fd); forward_all_io(master_fd); return await_child(child_pid); } else { /* child process */ close(master_fd); become_session_leader(); prepare_slave_pseudo_terminal_fds(slave_name); close(slave_fd); exec_command(&argv[optind]); } } /* vim: set et sw=4 sts=4 tw=79: */ yash-2.49/tests/pwd-y.tst000066400000000000000000000005601354143602500153560ustar00rootroot00000000000000# pwd-y.tst: yash-specific test of the pwd built-in test_Oe -e 2 'invalid option' pwd --no-such-option __IN__ pwd: `--no-such-option' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid operand' pwd unexpected_operand __IN__ pwd: no operand is expected __ERR__ test_O -d -e 1 'printing to closed stream' pwd >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/quote-p.tst000066400000000000000000000105011354143602500157040ustar00rootroot00000000000000# quote-p.tst: test of quoting for any POSIX-compliant shell posix="true" setup -d test_oE 'backslash (not preceding newline)' bracket \ \!\$x\%\&\(\)\*\+\,\-\.\/ \# \"x\" \'x\' bracket \0\1\2\3\4\5\6\7\8\9\:\;\<\=\>\? bracket \@\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z\[\]\^\_ \\ \\\\ bracket \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z\{\|\}\~ \`\` __IN__ [ !$x%&()*+,-./][#]["x"]['x'] [0123456789:;<=>?] [@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_][\][\\] [abcdefghijklmnopqrstuvwxyz{|}~][``] __OUT__ test_oE 'line continuation in normal word' bracket 123\ 456\ \ 789 \ ABC\ DEF __IN__ [123456789][ABC][DEF] __OUT__ test_OE -e 0 'line continuation in reserved word !' \ !\ false __IN__ test_oE 'line continuation in reserved words { }' \ {\ echo 1 \ }\ || \ {\ echo 2 \ }\ __IN__ 1 __OUT__ test_oE 'line continuation in reserved words case in esac and operator ;;' \ c\ a\ s\ e\ a \ i\ n\ (a) echo 1\ ;\ ;\ e\ s\ a\ c\ __IN__ 1 __OUT__ test_oE 'line continuation in reserved words for in do done' \ f\ o\ r\ \ \ i\ \ i\ n\ \ 1 2 \ d\ o\ \ echo $i \ d\ o\ n\ e\ __IN__ 1 2 __OUT__ test_oE 'line continuation in reserved words if then elif else fi' \ i\ f\ \ false \ t\ h\ e\ n\ \ echo 1 e\ l\ i\ f\ true \ t\ h\ e\ n\ \ echo 2 e\ l\ s\ e\ \ echo 3 \ f\ i\ \ __IN__ 2 __OUT__ test_oE 'line continuation in reserved words while do done' \ w\ h\ i\ l\ e\ \ true \ d\ o\ \ echo 1 break \ d\ o\ n\ e\ \ __IN__ 1 __OUT__ test_oE 'line continuation in reserved words until do done' \ u\ n\ t\ i\ l\ \ false \ d\ o\ \ echo 1 break \ d\ o\ n\ e\ \ __IN__ 1 __OUT__ test_oE 'line continuation in operators && ||' true\ &\ &\ \ false\ |\ |\ \ echo 1 __IN__ 1 __OUT__ test_oE 'line continuation in function definition' \ f\ un\ c \ ( \ ) \ # comment \ ( echo ok ) func __IN__ ok __OUT__ test_oE 'line continuation in operators <> >> <& >&' echo 1 >redir echo 2 \ 3\ >\ >\ \ redir \ >\ &\ \ 3 cat \ 4\ <\ >\ redir \ <\ &\ \ 4 __IN__ 1 2 __OUT__ test_oE 'line continuation in operator >|' -C echo XXX >clobber echo foo \ >\ |\ \ clobber cat clobber __IN__ foo __OUT__ test_oE 'line continuation in here-document operators' cat \ <\ <\ \ E\ N\ D\ foo END cat \ <\ <\ -\ \ E\ O\ F\ bar EOF __IN__ foo bar __OUT__ test_oE 'line continuation in assignment' fo\ o\ =\ b\ ar echo $foo __IN__ bar __OUT__ test_oE 'line continuation in parameter expansion' f=foo # echo $f ${f} ${#f} ${f#f} ${f:+x} echo \ \ $\ \ f $\ \ {\ \ f\ \ } $\ {\ \ #\ f\ } $\ {\ f\ \ #\ \ f\ \ } $\ {\ f\ \ :\ \ +\ \ x\ \ } __IN__ foo foo 3 oo x __OUT__ test_oE 'line continuation in arithmetic expansion' echo \ $\ \ (\ \ (\ \ 1\ \ \ + \ \ 2\ \ )\ \ ) __IN__ 3 __OUT__ test_oE 'line continuation in command substitution $(...)' echo \ $\ \ (\ \ echo 1\ \ ) __IN__ 1 __OUT__ test_oE 'line continuation in command substitution `...`' echo \ `\ \ echo 1\ \ ` __IN__ 1 __OUT__ test_oE 'single quotes' bracket 'abc' '"a"' 'a\\b' 'a''''''b' bracket 'a b' 'a b' __IN__ [abc]["a"][a\\b][ab] [a b][a b] __OUT__ test_oE 'double quotes' bracket "abc" "'a'" bracket "a b" "a b" __IN__ [abc]['a'] [a b][a b] __OUT__ test_oE 'expansions in double quotes' a=variable bracket "$a" "${a}" "$(echo command)" "`echo command`" "$((1+10))" __IN__ [variable][variable][command][command][11] __OUT__ test_oE 'double quotes in command substitution in double quotes' bracket "$(bracket "foo echo ")" __IN__ [[foo echo ]] __OUT__ test_oE 'aliases are ignored in command substitution in double quotes' alias echo=')' f() { bracket "$(echo x)"; } unalias echo f __IN__ [x] __OUT__ test_oE 'backslashes in double quotes' bracket "a\\b" "a\\\\b" bracket "a\$b" "a\`b\`c" "a\"b\"c" bracket "a\ b\ c" bracket "\ \!\#\$x\%\&\'\(\)\*\+\,\-\.\/" bracket "\0\1\2\3\4\5\6\7\8\9\:\;\<\=\>\?" bracket "\@\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z\[\\\]\^\_" bracket "\`\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z\{\|\}\~\`" bracket "a b" __IN__ [a\b][a\\b] [a$b][a`b`c][a"b"c] [abc] [\ \!\#$x\%\&\'\(\)\*\+\,\-\.\/] [\0\1\2\3\4\5\6\7\8\9\:\;\<\=\>\?] [\@\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z\[\\]\^\_] [`\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z\{\|\}\~`] [a b] __OUT__ test_oE 'quoted words are not reserved words' echo echo if command >if chmod a+x if PATH=.:$PATH \if "i"f i'f' __IN__ if command if command if command __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/quote-y.tst000066400000000000000000000017371354143602500157300ustar00rootroot00000000000000# quote-y.tst: yash-specific test of quoting test_oE 'backslash preceding EOF is ignored' "$TESTEE" -c 'printf "[%s]\n" 123\' __IN__ [123] __OUT__ test_oE 'line continuation in function definition' \ f\ u\ n\ c\ t\ i\ o\ \ n\ \ f"u\ n"c \ (\ )\ \ { echo foo; } func __IN__ foo __OUT__ test_oE 'line continuation in parameter expansion' f=foo # echo ${#?} ${${f}} ${f[1,2]:+x} echo \ $\ {\ \ #\ \ ?\ \ } $\ \ {\ \ $\ \ {\ \ f\ \ }\ \ } $\ {\ f\ \ [\ \ 1\ \ ,\ \ 2\ \ ]\ \ :\ \ +\ \ x\ \ } __IN__ 1 foo x __OUT__ test_Oe -e 2 'unclosed single quotation' echo 'foo - __IN__ syntax error: the single quotation is not closed __ERR__ #' test_Oe -e 2 'unclosed double quotation (direct)' echo "foo __IN__ syntax error: the double quotation is not closed __ERR__ #" test_Oe -e 2 'unclosed double quotation (in parameter expansion)' echo ${foo-"bar} __IN__ syntax error: the double quotation is not closed syntax error: `}' is missing __ERR__ #' #` #" # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/random-y.tst000066400000000000000000000015061354143602500160450ustar00rootroot00000000000000# random-y.tst: test of the $RANDOM special variable test_x 'RANDOM yields random numbers' until [ "$RANDOM" -ne "$RANDOM" ]; do :; done until [ "$((RANDOM % 7))" -eq 0 ]; do :; done __IN__ test_x -e 0 'assigning seed to RANDOM' -e print() { RANDOM=123 echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM RANDOM=456 echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM } print > seeded1 print > seeded2 diff seeded1 seeded2 __IN__ test_o 'assigning non-seed to RANDOM' (RANDOM=; echo [$RANDOM]) (RANDOM=X; echo [$RANDOM]) __IN__ [] [X] __OUT__ test_o 'unset RANDOM' unset RANDOM echo ${RANDOM-unset} RANDOM=123 echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM __IN__ unset 123 123 123 123 123 __OUT__ test_x 'read-only RANDOM' readonly RANDOM until [ "$RANDOM" -ne "$RANDOM" ]; do :; done __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/read-p.tst000066400000000000000000000117011354143602500154650ustar00rootroot00000000000000# read-p.tst: test of the read built-in for any POSIX-compliant shell posix="true" setup -d test_oE 'single operand - without IFS' read a <<\END A END echoraw $? "[${a-unset}]" __IN__ 0 [A] __OUT__ test_oE 'single operand - with IFS whitespace' read a <<\END A END echoraw $? "[${a-unset}]" __IN__ 0 [A] __OUT__ test_oE 'single operand - with IFS non-whitespace' read a <<\END - A - END echoraw $? "[${a-unset}]" __IN__ 0 [- A -] __OUT__ test_oE 'EOF fails read' ! read a withoutp.out readonly -p >withp.out diff withoutp.out withp.out __IN__ ) test_oE -e 0 'assigning empty value' readonly a= readonly -p a __IN__ readonly a='' __OUT__ test_o -d 'reusing printed read-only variables' eval ":; $( readonly a=A readonly -p )" echo 1 $a a=X && echo not reached __IN__ 1 A __OUT__ test_oE 'making read-only with -p (variables)' readonly -p a=A readonly -p a __IN__ readonly a=A __OUT__ test_Oe -e 1 'assigning to read-only variable' readonly a=A readonly a=X __IN__ readonly: $a is read-only __ERR__ test_OE -e 0 'assigning to variable with empty name' readonly =X # This succeeds, but the variable can never be used. __IN__ test_Oe -e 1 'making array read-only' -e a=(1) readonly a readonly a=1 __IN__ readonly: $a is read-only __ERR__ testcase "$LINENO" 'making function read-only' \ 3<<\__IN__ 4<<\__OUT__ 5<<__ERR__ foo() { echo foo 1; } bar() { echo bar 1; } readonly -f bar foo() { echo foo 2; } bar() { echo bar 2; } echo $? foo bar __IN__ 2 foo 2 bar 1 __OUT__ $testee: function \`bar' cannot be redefined because it is read-only __ERR__ ( setup 'foo() { echo foo; }; bar() { echo bar; }; baz() { echo baz; }' setup 'readonly -f foo bar' test_x -e 0 'printing all read-only functions: exit status' readonly -fp __IN__ test_oE 'printing all read-only functions: output' readonly -fp | sed 's;^[[:space:]]*;;g' __IN__ bar() { echo bar } readonly -f bar foo() { echo foo } readonly -f foo __OUT__ test_x -e 0 'printing specific read-only functions: exit status' readonly -fp bar __IN__ test_oE 'printing specific read-only functions: output' readonly -fp bar | sed 's;^[[:space:]]*;;g' __IN__ bar() { echo bar } readonly -f bar __OUT__ test_OE -e 0 'without argument, -p is assumed (functions)' readonly -f >withoutp.out readonly -f -p >withp.out diff withoutp.out withp.out __IN__ ) test_o -d 'reusing printed read-only functions' eval "$( func() { echo func; } readonly -f func readonly -fp func )" func func() { :; } func __IN__ func func __OUT__ ( posix="true" test_Oe -e 2 'invalid option -f (POSIX)' readonly -f __IN__ readonly: `-f' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option -x (POSIX)' readonly -x __IN__ readonly: `-x' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option -X (POSIX)' readonly -X __IN__ readonly: `-X' is not a valid option __ERR__ #' #` ) test_Oe -e 2 'invalid option -z' readonly -z __IN__ readonly: `-z' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' readonly --no-such=option __IN__ readonly: `--no-such=option' is not a valid option __ERR__ #' #` test_O -d -e 1 'printing to closed stream' readonly a=X readonly >&- __IN__ test_Oe -e 1 'printing non-existing variable' unset a readonly -p a __IN__ readonly: no such variable $a __ERR__ test_Oe -e 1 'printing non-existing function' readonly -fp a __IN__ readonly: no such function `a' __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/redir-p.tst000066400000000000000000000207521354143602500156650ustar00rootroot00000000000000# redir-p.tst: test of redirections for any POSIX-compliant shell posix="true" exec 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&- echo in0 >in0 echo in1 >in1 echo in2 >in2 echo in3 >in3 echo 'in*' >'in*' test_o 'quoted file descriptor' echo \2>quotedfd echo --- cat quotedfd __IN__ --- 2 __OUT__ test_oE 'file descriptor must immediately precede operator' echo 1 >precede1 echo 2>precede2 echo --- cat precede1 echo ---- cat precede2 __IN__ --- 1 ---- __OUT__ test_o 'file descriptors up to 9 are supported' cat 9multidigit # should not be interpreted as "echo 1 1>multidigit" test "$(cat multidigit)" != 1 __IN__ test_oE -e 0 'tilde expansion in redirection operand' HOME=$PWD cat <~/in0 __IN__ in0 __OUT__ test_oE -e 0 'parameter expansion in redirection operand' i=xxxin0 cat <${i#xxx} __IN__ in0 __OUT__ test_oE -e 0 'command substitution in redirection operand' cat <`echo in`$(echo 0) __IN__ in0 __OUT__ test_oE -e 0 'arithmetic expansion in redirection operand' cat /dev/null 3>&1 2>&3 3>&- 1>&- 2>&1 __IN__ test_oE 'input redirection, success' cat 0overwrite1 echo bar 1>overwrite2 >overwrite2 echo --- cat overwrite1 overwrite2 __IN__ --- foo __OUT__ test_oE 'overwrite redirection, -C, success' -C echo foo >overwrite3 # non-existing file echo bar >/dev/null # existing non-regular file echo --- cat overwrite3 __IN__ --- foo __OUT__ test_O -d -e n 'overwrite redirection, -C, existing file error' -C echo foo >overwrite4 echo boo >overwrite4 __IN__ test_o 'overwrite redirection, -C, existing file not modified' -C echo foo >overwrite5 echo boo >overwrite5 || : echo --- cat overwrite5 __IN__ --- foo __OUT__ test_O -d -e n 'overwrite redirection, creation failure' >_no_such_dir_/foo __IN__ test_oE 'clobbering redirection, +C, success' echo foo >|clobber1 echo bar 1>|clobber2 >|clobber2 echo --- $? cat overwrite1 overwrite2 __IN__ --- 0 foo __OUT__ test_oE 'clobbering redirection, -C, success' -C echo foo >|clobber3 # non-existing file echo --- $? echo bar >|/dev/null # existing non-regular file echo ---- $? cat clobber3 echo ----- >|clobber3 # existing file echo ------ $? cat clobber3 __IN__ --- 0 ---- 0 foo ----- ------ 0 __OUT__ test_O -d -e n 'clobbering redirection, creation failure' >|_no_such_dir_/foo __IN__ test_oE 'appending redirection, success, new file' echo foo >>append1 echo --- $? cat append1 __IN__ --- 0 foo __OUT__ test_oE 'appending redirection, success, existing file' echo foo >>append2 echo --- $? echo bar >>append2 echo ---- $? cat append2 __IN__ --- 0 ---- 0 foo bar __OUT__ test_oE 'effect of appending redirection' { echo foo >&3 && echo bar >&4 && echo baz >&3 && echo qux >&4 } 3>>append3 4>>append3 && cat append3 __IN__ foo bar baz qux __OUT__ test_oE -e 0 'in-out redirection, success' echo foo 1<>inout1 echo --- $? cat <>inout1 __IN__ --- 0 foo __OUT__ test_O -d -e n 'in-out redirection, failure' <>_no_such_dir_/foo __IN__ test_oE -e 0 'input duplication, success' cat 3/dev/null <&3 __IN__ test_OE -e 0 'input closing, success, open file descriptor' <&- && 0<&- __IN__ test_OE -e 0 'input closing, success, closed file descriptor' <&- <&- __IN__ test_O -e n 'effect of input closing' cat <&- __IN__ test_oE 'output duplication, success' echo foo 3>dup1 >&3 echo --- $? echo bar 3>>dup1 1>&"$((1+2))" echo ---- $? cat dup1 __IN__ --- 0 ---- 0 foo bar __OUT__ ( setup 'exec 3>&-' test_O -d -e n 'output duplication, failure (closed file descriptor)' >&3 __IN__ ) test_O -d -e n 'output duplication, failure (unreadable file descriptor)' 3&3 __IN__ test_OE -e 0 'output closing, success, open file descriptor' >&- && 1>&- __IN__ test_OE -e 0 'output closing, success, closed file descriptor' >&- >&- __IN__ test_O -e n 'effect of output closing' echo >&- __IN__ test_oE -e 0 'effect of here-document' cat <longhere test_oE -e 0 'long here-document' -e . ./longhere | while read -r i; do test "$i" -eq "${j:=0}" j=$((j+1)) done __IN__ __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/redir-y.tst000066400000000000000000000220461354143602500156740ustar00rootroot00000000000000# redir-y.tst: yash-specific test of redirections exec 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&- echo in0 >in0 echo in1 >in1 echo 'in*' >'in*' echo PS1= >yashrc test_oE -e 0 \ 'pathname expansion in redirection operand (non-POSIX, interactive), success'\ -i +m --rcfile=yashrc cat /dev/null <&3 __IN__ test_OE -e 0 'output duplication of unreadable file descriptor' 3&3 __IN__ test_oE -e 0 'tilde expansion not performed in here-document operand' HOME=/home cat <<~ /home ~ __IN__ /home __OUT__ test_oE -e 0 'parameter expansion not performed in here-document operand' foo=FOO cat <<$foo FOO $foo __IN__ FOO __OUT__ test_oE -e 0 'command substitution not performed in here-document operand' cat <<$(echo foo)`echo bar` foobar $(echo foo)`echo bar` __IN__ foobar __OUT__ test_oE -e 0 'arithmetic expansion not performed in here-document operand' cat <<$((1+1)) foo $((1+1)) __IN__ foo __OUT__ test_oE -e 0 'complex expansion in here-document' -s 1 '" "' 3 IFS=- printf '[%s]' ${1+"$@"}; echo printf '[%s]' "${1+"$@"}"; echo cat <&1 __IN__ foo __OUT__ # Test various file descriptor combinations to ensure the shell correctly # "dup"s the pipe file descriptors after opening a pipe. test_oE 'pipe redirection' exec 4>>|3; echo 4-3 >&4; exec 4>&-; cat <&3; exec 3<&- exec 4>>|6; echo 4-6 >&4; exec 4>&-; cat <&6; exec 6<&- exec 5>>|3; echo 5-3 >&5; exec 5>&-; cat <&3; exec 3<&- exec 5>>|6; echo 5-6 >&5; exec 5>&-; cat <&6; exec 6<&- exec 5>>|4; echo 5-4 >&5; exec 5>&-; cat <&4; exec 4<&- exec 3>>|6; echo 3-6 >&3; exec 3>&-; cat <&6; exec 6<&- exec 3>>|4; echo 3-4 >&3; exec 3>&-; cat <&4; exec 4<&- exec 9>&1 exec >>|0; echo 1-0 ; exec >&9; cat <&0; __IN__ 4-3 4-6 5-3 5-6 5-4 3-6 3-4 1-0 __OUT__ test_oE 'using pipe redirection in subshell' exec 3>&1 { while read -r i && [ "$i" -lt 5 ]; do echo "$i" >&3 echo "$((i+1))" done | { echo 0 cat -u } } >>|0 __IN__ 0 1 2 3 4 __OUT__ test_oE -e 0 'input process redirection' -e cat <(echo foo) cat <(echo bar)- __IN__ foo bar __OUT__ test_oE -e 0 'output process redirection' -e echo >(read i && echo $((i+1)))99 | cat # "cat" ensures the output is flushed before the shell exits __IN__ 100 __OUT__ test_oE -e 0 'process redirection is run in subshell' -e i=0 echo >(read i && echo $((i+1))) 99 | cat # "cat" ensures the output is flushed before reaching this line echo $i __IN__ 100 0 __OUT__ test_x -e 0 'exit status of process redirection is ignored' <(false) >(false) __IN__ test_oE -e 0 'empty here-string' cat <<<"" __IN__ __OUT__ test_oE -e 0 'tilde expansion in here-string' HOME=/home cat <<<~/foo __IN__ /home/foo __OUT__ test_oE -e 0 'double quotation and parameter expansion in here-string' foo=FOOOO cat <<<"${foo%OO}" __IN__ FOO __OUT__ test_oE -e 0 'multi-line here-string' cat <<<'1 2' __IN__ 1 2 __OUT__ test_oE -e 0 'space after here-string operator' cat <<< foo __IN__ foo __OUT__ test_oE -e 0 'here-string with non-default file descriptor' cat 3<< 1> 2< 2>>3 <<4 4 __IN__ ( posix="true" test_Oe -e 2 'IO_NUMBER as redirection operand (filename)' # Here the token "12" is an IO_NUMBER token for the second redirection, and # hence cannot be the operand of the first. > 12> 34 __IN__ syntax error: put a space between `2' and `>' for disambiguation __ERR__ #' #` #' #` test_Oe -e 2 'IO_NUMBER as redirection operand (here-document)' # Here the token "01" is an IO_NUMBER token for the second redirection, and # hence cannot be the operand of the first. << 01<< 23 01 23 __IN__ syntax error: put a space between `1' and `<' for disambiguation syntax error: here-document content for <<01 is missing syntax error: here-document content for <<23 is missing __ERR__ #' #` #' #` ) test_Oe -e 2 'missing target for <' < __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for <>' <> __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for <& (EOF)' <& __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for <& (line continuation and comment)' <&\ # __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for >' > __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for >|' >| __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for >&' >& __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for >>' >> __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for >>|' >>| __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing target for <<<' <<< __IN__ syntax error: the redirection target is missing __ERR__ test_Oe -e 2 'missing delimiter after <<' << __IN__ syntax error: the end-of-here-document indicator is missing __ERR__ test_Oe -e 2 'missing delimiter after <<-' <<- __IN__ syntax error: the end-of-here-document indicator is missing __ERR__ test_Oe -e 2 'newline in end-of-here-document indicator' <<' ' __IN__ syntax error: the end-of-here-document indicator contains a newline __ERR__ test_Oe -e 2 'unclosed here-document (unquoted)' cat <( __IN__ syntax error: unclosed process redirection __ERR__ ( posix="true" test_Oe -e 2 'no process redirection <() in POSIX mode' <() __IN__ syntax error: process redirection is not supported in the POSIXly-correct mode __ERR__ test_Oe -e 2 'no process redirection >() in POSIX mode' >() __IN__ syntax error: process redirection is not supported in the POSIXly-correct mode __ERR__ test_Oe -e 2 'no pipe redirection in POSIX mode' >>|0 __IN__ syntax error: pipe redirection is not supported in the POSIXly-correct mode __ERR__ test_Oe -e 2 'no here-string in POSIX mode' <</dev/null } __IN__ syntax error: unexpected word after redirection syntax error: (maybe you missed `;'?) __ERR__ #' #` #) test_Oe -e 2 'keyword after redirection on subshell (POSIX)' { (echo not printed) >/dev/null } __IN__ syntax error: unexpected word after redirection syntax error: (maybe you missed `;'?) __ERR__ #' #` #) # Some relevant tests in grouping-y.tst. ) test_OE -e 0 'keyword after redirection in compound command (non-POSIX)' { { echo not printed; } >/dev/null } { ( echo not printed ) >/dev/null } __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/resetsig.c000066400000000000000000000026541354143602500155610ustar00rootroot00000000000000/* resetsig.c: invokes command with all signal handlers reset */ /* (C) 2009-2011 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _XOPEN_SOURCE 600 #include #include #include #include "../siglist.h" int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "resetsig: too few arguments\n"); return 2; } struct sigaction action; action.sa_handler = SIG_DFL; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigprocmask(SIG_SETMASK, &action.sa_mask, NULL); for (const signal_T *s = signals; s->no != 0; s++) if (s->no != SIGKILL && s->no != SIGSTOP) sigaction(s->no, &action, NULL); execvp(argv[1], &argv[1]); perror("invoke: exec failed"); return 126; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/tests/return-p.tst000066400000000000000000000076031354143602500160770ustar00rootroot00000000000000# return-p.tst: test of the return built-in for any POSIX-compliant shell posix="true" test_oE 'returning from function, unnested' fn() { echo in function return echo not reached } fn echo after function __IN__ in function after function __OUT__ test_oE 'returning from function, nested in other functions' first=true fn1() { echo in fn1 if $first; then first=false else return fi echo recurring fn1 echo recurred } fn2() { echo in fn2 fn1 echo out fn2 } fn2 echo after function __IN__ in fn2 in fn1 recurring in fn1 recurred out fn2 after function __OUT__ cat <<\__END__ >fn fn() { echo in function return echo out function, not reached } fn echo after function __END__ test_oE 'returning from function, nested in dot script' . ./fn echo after dot __IN__ in function after function after dot __OUT__ cat <<\__END__ >return echo in return return echo out return, not reached __END__ test_oE 'returning from dot script, unnested' . ./return echo after . __IN__ in return after . __OUT__ cat <<\__END__ >outer echo in outer . ./return echo out outer __END__ test_oE 'returning from dot script, nested in another dot script' . ./outer echo after . __IN__ in outer in return out outer after . __OUT__ test_oE 'returning from dot script, nested in function' fn() { echo in function . ./return echo out function } fn echo after function __IN__ in function in return out function after function __OUT__ test_OE -e 13 'default exit status of returning from function' fn() { (exit 13) return } fn __IN__ cat <<\__END__ >exitstatus (exit 17) return __END__ test_OE -e 17 'default exit status of returning from dot script' . ./exitstatus __IN__ test_OE -e 13 'specifying exit status in returning from function' fn() { (exit 1) return 13 } fn __IN__ cat <<\__END__ >exitstatus17 (exit 1) return 17 __END__ test_OE -e 17 'specifying exit status in returning from dot script' . ./exitstatus17 __IN__ test_oE -e 0 'default exit status in trap' fn() { true; return; } trap 'fn; echo trapped $?' USR1 (exit 19) (kill -s USR1 $$; exit 19) : # null command to ensure the trap to be handled __IN__ trapped 19 __OUT__ test_OE 'returning out of eval' fn() { eval return echo not reached } fn __IN__ test_OE 'returning with !' fn() { ! return echo not reached } fn __IN__ test_OE 'returning before &&' fn() { return && echo not reached 1 echo not reached 2 $? } fn __IN__ test_OE 'returning after &&' fn() { true && return echo not reached $? } fn __IN__ test_OE 'returning before ||' fn() { return || echo not reached 1 echo not reached 2 $? } fn __IN__ test_OE 'returning after ||' fn() { false || return echo not reached $? } fn __IN__ test_OE 'returning out of brace' fn() { { return; } echo not reached } fn __IN__ test_OE 'returning out of if' fn() { if return; then echo not reached then; else echo not reached else; fi echo not reached } fn __IN__ test_OE 'returning out of then' fn() { if true; then return; echo not reached then; else echo not reached else; fi echo not reached } fn __IN__ test_OE 'returning out of else' fn() { if false; then echo not reached then; else return; echo not reached else; fi echo not reached } fn __IN__ test_OE 'returning out of for loop' fn() { for i in 1; do return echo not reached in loop done echo not reached after loop } fn __IN__ test_OE 'returning out of while loop' fn() { while true; do return echo not reached in loop done echo not reached after loop } fn __IN__ test_OE 'returning out of until loop' fn() { until false; do return echo not reached in loop done echo not reached after loop } fn __IN__ test_OE 'returning out of case' fn() { case x in x) return echo not reached in case esac echo not reached after esac } fn __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/return-y.tst000066400000000000000000000051731354143602500161100ustar00rootroot00000000000000# return-y.tst: yash-specific test of the return built-in test_OE -e 13 'returning from shell, non-interactive' return 13 echo not reached __IN__ test_o -d 'returning from shell, interactive' -i +m return 13 echo continued $? __IN__ continued 1 __OUT__ test_O -e 7 'returning from function, interactive' -i +m func() { return 7; echo not reached; } func __IN__ ##### cat <<\__END__ >return echo in return return echo out return, not reached __END__ cat <<\__END__ >noreturn echo noreturn __END__ ( posix="true" export ENV="$PWD/return" test_o 'returning from initialization script ($ENV)' -i +m __IN__ in return __OUT__ ) test_o 'returning from profile script' \ -il +m --profile="$PWD/return" --rcfile="$PWD/noreturn" __IN__ in return noreturn __OUT__ test_o 'returning from rcfile script' \ -il +m --profile="$PWD/noreturn" --rcfile="$PWD/return" __IN__ noreturn in return __OUT__ test_o 'returning from dot script, interactive' -i +m . ./return . ./noreturn __IN__ in return noreturn __OUT__ ##### # This test depends on the fact that yash handles USR1 before USR2 # when the two signals are caught while executing a single command. test_oE 'returning from trap' trap 'echo USR1; return 13; echo not reached in trap' USR1 trap 'echo USR2' USR2 k() { (kill -s USR1 $$; kill -s USR2 $$) echo done } k __IN__ USR1 USR2 done __OUT__ ##### test_OE -e 7 'returning out of eval (iteration)' fn() { eval -i 'return 7' 'echo not reached 1' echo not reached 2 } fn __IN__ test_O -e 127 'returning from auxiliary (iteration)' COMMAND_NOT_FOUND_HANDLER=('return 1' 'echo not reached $?') ./_no_such_command_ __IN__ ##### test_oE 'using -n option' return -n 7 echo $? return -n 11 echo $? return --no-return 11 echo $? __IN__ 7 11 11 __OUT__ test_oE -- 'not returning from function with -n' fn() { return -n 7 echo $? } fn __IN__ 7 __OUT__ ##### test_Oe -e n 'too many operands' return 1 2 __IN__ return: too many operands are specified __ERR__ test_Oe -e n 'invalid operand: not a integer' return x echo not reached __IN__ return: `x' is not a valid integer __ERR__ #' #` test_Oe -e n 'invalid operand: negative integer' return -- -100 echo not reached __IN__ return: `-100' is not a valid integer __ERR__ #' #` test_Oe -e n 'invalid operand: too large integer' return 999999999999999999999999999999999999999999999999999999999999999999999999 __IN__ return: `999999999999999999999999999999999999999999999999999999999999999999999999' is not a valid integer __ERR__ #' #` test_Oe -e n 'invalid option' return --no-such-option '' __IN__ return: `--no-such-option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/run-test.sh000066400000000000000000000252311354143602500157010ustar00rootroot00000000000000# run-test.sh: runs a set of test cases # (C) 2016-2019 magicant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # This script expects two operands. # The first is the pathname to the testee, the shell that is to be tested. # The second is the pathname to the test file that defines test cases. # The result is output to a result file whose name is given by replacing the # extension of the test file with ".trs". # If any test case fails, it is also reported to the standard error. # If the -r option is specified, intermediate files are not removed. # If the -v option is specified, the testee is tested by Valgrind. # The exit status is zero unless a critical error occurs. Failure of test cases # does not cause the script to return non-zero. set -Ceu umask u+rwx ##### Some utility functions and aliases eprintf() { printf "$@" >&2 } # $1 = pathname absolute() case "$1" in (/*) printf '%s\n' "$1";; (*) printf '%s/%s' "${PWD%/}" "$1";; esac ##### Script startup # require yash for alias support and ulimit built-ins if ! [ "${YASH_VERSION-}" ]; then eprintf '%s: must be run with yash\n' "$0" exit 64 # sysexits.h EX_USAGE fi command -b ulimit -c 0 2>/dev/null || : exec &- 4>&- 5>&- # ensure correctness of $PWD cd -L . remove_work_dir="true" use_valgrind="false" while getopts rv opt; do case $opt in (r) remove_work_dir="false";; (v) use_valgrind="true";; (*) exit 64 # sysexits.h EX_USAGE esac done shift "$((OPTIND-1))" testee="$(command -v "${1:?testee not specified}")" test_file="${2:?test file not specified}" exec >|"${test_file%.*}.trs" export LC_CTYPE="${LC_ALL-${LC_CTYPE-$LANG}}" export LANG=C export YASH_LOADPATH= # ignore default yashrc unset -v CDPATH COLUMNS COMMAND_NOT_FOUND_HANDLER DIRSTACK ECHO_STYLE ENV unset -v FCEDIT HANDLED HISTFILE HISTRMDUP HISTSIZE HOME IFS LC_ALL unset -v LC_COLLATE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME LINES MAIL unset -v MAILCHECK MAILPATH NLSPATH OLDPWD OPTARG PROMPT_COMMAND unset -v PS1 PS1R PS1S PS2 PS2R PS2S PS3 PS3R PS3S PS4 PS4R PS4S unset -v RANDOM TERM YASH_AFTER_CD YASH_LE_TIMEOUT YASH_VERSION unset -v A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ unset -v a b c d e f g h i j k l m n o p q r s t u v w x y z unset -v posix skip export -X LINENO OPTIND ##### Prepare temporary directory work_dir="tmp.$$" rm_work_dir() if "$remove_work_dir"; then if [ -d "$work_dir" ]; then chmod -R a+rX "$work_dir"; fi rm -fr "$work_dir" fi trap rm_work_dir EXIT trap 'rm_work_dir; trap - INT; kill -INT $$' INT trap 'rm_work_dir; trap - TERM; kill -TERM $$' TERM trap 'rm_work_dir; trap - QUIT; kill -QUIT $$' QUIT mkdir "$work_dir" ##### Some more utilities { if diff -U 10000 /dev/null /dev/null; then diff_opt='-U 10000' elif diff -C 10000 /dev/null /dev/null; then diff_opt='-C 10000' else diff_opt='' fi } >/dev/null 2>&1 setup_script="" # Add a setup script that is run before each test case # If the first argument is "-" or omitted, the script is read from stdin. # If the first argument is "-d", the default utility functions are added. # Otherwise, the first argument is added as the script. setup() { case "${1--}" in (-) setup "$(cat)" ;; (-d) setup <<\END _empty= _sp=' ' _tab=' ' _nl=' ' echoraw() { printf '%s\n' "$*" } bracket() { if [ $# -gt 0 ]; then printf '[%s]' "$@"; fi echo } END ;; (*) setup_script="$setup_script $1" ;; esac } # Invokes the testee. # If the "posix" variable is defined non-empty, the testee is invoked as "sh". # If the "use_valgrind" variable is true, Valgrind is used to run the testee, # in which case the testee will ignore argv[0]. testee() ( if [ "${posix:+set}" = set ]; then testee="$testee_sh" export TESTEE="$testee" fi if ! "$use_valgrind"; then exec "$testee" "$@" else test -r "$abs_suppressions" || abs_suppressions= exec valgrind --leak-check=full --vgdb=no --log-fd=17 \ ${abs_suppressions:+--suppressions="$abs_suppressions"} \ --gen-suppressions=all \ "$testee" "$@" \ 17>>"${valgrind_file-0.valgrind}" fi ) # The test case runner. # # Contents of file descriptor 3 are passed to the standard input of a newly # invoked testee using a temporary file. # Contents of file descriptor 4 and 5 are compared to the actual output from # the standard output and error of the testee, respectively, if those file # descriptors are open. If they differ from the expected, the test case fails. # # The first argument is treated as the line number where the test case appears # in the test file. As remaining arguments, options and operands may follow. # # If the "-d" option is specified, the test case fails unless the actual output # to the standard error is non-empty. File descriptor 5 is ignored. # # If the "-e " option is specified, the exit status of # the testee is also checked. If the actual exit status differs from the # expected, the test case fails. If is "n", the expected # is any non-zero exit status. If is a signal name (w/o # the SIG-prefix), the testee is expected to be killed by the signal. # # The first operand is used as the name of the test case. # The remaining operands are passed as arguments to the testee. # # If the "skip" variable is defined non-empty, the test case is skipped. testcase() { test_lineno="${1:?line number unspecified}" shift 1 OPTIND=1 diagnostic_required="false" expected_exit_status="" while getopts de: opt; do case $opt in (d) diagnostic_required="true";; (e) expected_exit_status="$OPTARG";; (*) return 64 # sysexits.h EX_USAGE esac done shift "$((OPTIND-1))" test_case_name="${1:?unnamed test case \($test_file:$test_lineno\)}" shift 1 log_stdout() { printf '%%%%%% %s: %s:%d: %s\n' \ "$1" "$test_file" "$test_lineno" "$test_case_name" } in_file="$test_lineno.in" out_file="$test_lineno.out" err_file="$test_lineno.err" valgrind_file="$test_lineno.valgrind" # prepare input file { if [ "$setup_script" ]; then printf '%s\n' "$setup_script" fi cat <&3 } >"$in_file" chmod u+r "$in_file" if [ "${skip-}" ]; then log_stdout SKIPPED echo return fi if [ -e "$out_file" ]; then printf 'Output file %s already exists.\n' "$out_file" return 1 fi if [ -e "$err_file" ]; then printf 'Output file %s already exists.\n' "$err_file" return 1 fi # run the testee log_stdout START set +e # Output files are opened in append mode to ensure write atomicity. testee "$@" <"$in_file" >>"$out_file" 2>>"$err_file" 3>&- 4>&- 5>&- actual_exit_status="$?" set -e chmod u+r "$out_file" "$err_file" failed="false" # check exit status exit_status_fail() { failed="true" eprintf '%s:%d: %s: exit status mismatch\n' \ "$test_file" "$test_lineno" "$test_case_name" } case "$expected_exit_status" in ('') ;; (n) printf '%% exit status: expected=non-zero actual=%d\n\n' \ "$actual_exit_status" if [ "$actual_exit_status" -eq 0 ]; then exit_status_fail fi ;; ([[:alpha:]]*) printf '%% exit status: expected=%s ' "$expected_exit_status" if [ "$actual_exit_status" -le 128 ] || ! actual_signal="$(kill -l "$actual_exit_status" \ 2>/dev/null)"; then printf 'actual=%d\n\n' "$actual_exit_status" exit_status_fail else printf 'actual=%d(%s)\n\n' \ "$actual_exit_status" "$actual_signal" if [ "$actual_signal" != "$expected_exit_status" ]; then exit_status_fail fi fi ;; (*) printf '%% exit status: expected=%d actual=%d\n\n' \ "$expected_exit_status" "$actual_exit_status" if [ "$actual_exit_status" -ne "$expected_exit_status" ]; then exit_status_fail fi ;; esac # check standard output if { exec <&4; } 2>/dev/null; then printf '%% standard output diff:\n' if ! diff $diff_opt - "$out_file"; then failed="true" eprintf '%s:%d: %s: standard output mismatch\n' \ "$test_file" "$test_lineno" "$test_case_name" fi echo fi # check standard error if "$diagnostic_required"; then printf '%% standard error (expecting non-empty output):\n' cat "$err_file" if ! [ -s "$err_file" ]; then failed="true" eprintf '%s:%d: %s: standard error mismatch\n' \ "$test_file" "$test_lineno" "$test_case_name" fi echo elif { exec <&5; } 2>/dev/null; then printf '%% standard error diff:\n' if ! diff $diff_opt - "$err_file"; then failed="true" eprintf '%s:%d: %s: standard error mismatch\n' \ "$test_file" "$test_lineno" "$test_case_name" fi echo fi # check Valgrind results if [ -f "$valgrind_file" ]; then chmod a+r "$valgrind_file" printf '%% Valgrind log:\n' cat "$valgrind_file" echo if grep -q 'valgrind: fatal error:' "$valgrind_file"; then # There was an error in Valgrind. Treat this test case as skipped. log_stdout SKIPPED echo return fi if grep 'ERROR SUMMARY:' "$valgrind_file" | grep -qv ' 0 errors'; then failed="true" eprintf '%s:%d: %s: Valgrind detected error\n' \ "$test_file" "$test_lineno" "$test_case_name" fi fi if "$failed"; then log_stdout FAILED else log_stdout PASSED fi echo } alias test_x='testcase "$LINENO" 3<<\__IN__' alias test_o='testcase "$LINENO" 3<<\__IN__ 4<<\__OUT__' alias test_O='testcase "$LINENO" 3<<\__IN__ 4 saveset saveset=$(set +o) set +aeu -f eval "$saveset" set -o | diff saveset - __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/set-y.tst000066400000000000000000000334551354143602500153700ustar00rootroot00000000000000# set-y.tst: yash-specific test of the set built-in # Many of the tests in this file are identical to those in set-p.tst. setup -d test_x -e 0 'printing all variables: exit status' set __IN__ test_oE 'printing all variables: output' yashtest1='foo' export yashtest2='"double"' readonly yashtest3="'single'" yashtest4='back\slash' unset yashtest5 yashtest6 export yashtest5 readonly yashtest6 yashtest0= set | grep '^yashtest' __IN__ yashtest0='' yashtest1=foo yashtest2='"double"' yashtest3=\'single\' yashtest4='back\slash' __OUT__ test_oE 'printing all variables: inside function' yashtest1=global f() { typeset yashtest2=local; set; } f | grep '^yashtest' __IN__ yashtest1=global yashtest2=local __OUT__ test_oE 'setting one positional parameter (no --)' -e set foo bracket "$@" __IN__ [foo] __OUT__ test_oE 'setting three positional parameters (no --)' -e set foo 'B A R' baz bracket "$@" __IN__ [foo][B A R][baz] __OUT__ test_oE 'setting empty positional parameters (no --)' -e set '' '' bracket "$@" __IN__ [][] __OUT__ test_oE 'setting zero positional parameters' -es 1 2 3 set -- echo $# __IN__ 0 __OUT__ test_oE 'setting three positional parameters (with --)' -e set -- - -- baz bracket "$@" __IN__ [-][--][baz] __OUT__ # $1 = $LINENO, $2 = short option, $3 = long option test_short_option_on() { testcase "$1" -e 0 "$3 (short) on: \$-" 3<<__IN__ set -$2 && printf '%s\n' "\$-" | grep -q $2 __IN__ } # $1 = $LINENO, $2 = short option, $3 = long option test_short_option_off() { testcase "$1" -e 0 "$3 (short) off: \$-" "-$2" 3<<__IN__ set +$2 && printf '%s\n' "\$-" | grep -qv $2 __IN__ } # $1 = $LINENO, $2 = short option, $3 = long option test_long_option_on() { testcase "$1" -e 0 "$3 (long) on: \$-" 3<<__IN__ set -o $3 && printf '%s\n' "\$-" | grep -q $2 __IN__ } # $1 = $LINENO, $2 = short option, $3 = long option test_long_option_off() { testcase "$1" -e 0 "$3 (long) off: \$-" "-$2" 3<<__IN__ set +o $3 && printf '%s\n' "\$-" | grep -qv $2 __IN__ } test_short_option_on "$LINENO" a allexport test_short_option_off "$LINENO" a allexport test_long_option_on "$LINENO" a allexport test_long_option_off "$LINENO" a allexport test_short_option_on "$LINENO" b notify test_short_option_off "$LINENO" b notify test_long_option_on "$LINENO" b notify test_long_option_off "$LINENO" b notify test_short_option_on "$LINENO" C noclobber test_short_option_off "$LINENO" C noclobber test_long_option_on "$LINENO" C noclobber test_long_option_off "$LINENO" C noclobber test_short_option_on "$LINENO" e errexit test_short_option_off "$LINENO" e errexit test_long_option_on "$LINENO" e errexit test_long_option_off "$LINENO" e errexit test_short_option_on "$LINENO" f noglob test_short_option_off "$LINENO" f noglob test_long_option_on "$LINENO" f noglob test_long_option_off "$LINENO" f noglob test_short_option_on "$LINENO" h hashondef test_short_option_off "$LINENO" h hashondef test_long_option_on "$LINENO" h hashondef test_long_option_off "$LINENO" h hashondef # The -m option cannot be tested here due to dependency on the terminal. test_short_option_on "$LINENO" n noexec test_short_option_off "$LINENO" n noexec # One can never reset the -n option #test_long_option_on "$LINENO" n noexec #test_long_option_off "$LINENO" n noexec test_short_option_on "$LINENO" u nounset test_short_option_off "$LINENO" u nounset test_long_option_on "$LINENO" u nounset test_long_option_off "$LINENO" u nounset test_short_option_on "$LINENO" v verbose test_short_option_off "$LINENO" v verbose test_long_option_on "$LINENO" v verbose test_long_option_off "$LINENO" v verbose test_short_option_on "$LINENO" x xtrace test_short_option_off "$LINENO" x xtrace test_long_option_on "$LINENO" x xtrace test_long_option_off "$LINENO" x xtrace # $1 = $LINENO, $2 = long option test_long_option_default_off() { testcase "$1" -e 0 "the $2 option is off by default" 3<<__IN__ save="\$(set +o)" && set +o $2 && test "\$(set +o)" = "\$save" && set -o $2 && test "\$(set +o)" != "\$save" && set -o no$2 && test "\$(set +o)" = "\$save" && set +o no$2 && test "\$(set +o)" != "\$save" && set ++$2 && test "\$(set +o)" = "\$save" && set --$2 && test "\$(set +o)" != "\$save" && set --no$2 && test "\$(set +o)" = "\$save" && set ++no$2 && test "\$(set +o)" != "\$save" __IN__ } # $1 = $LINENO, $2 = long option test_long_option_default_on() { testcase "$1" -e 0 "the $2 option is off by default" 3<<__IN__ save="\$(set +o)" && set -o $2 && test "\$(set +o)" = "\$save" && set +o $2 && test "\$(set +o)" != "\$save" && set +o no$2 && test "\$(set +o)" = "\$save" && set -o no$2 && test "\$(set +o)" != "\$save" && set --$2 && test "\$(set +o)" = "\$save" && set ++$2 && test "\$(set +o)" != "\$save" && set ++no$2 && test "\$(set +o)" = "\$save" && set --no$2 && test "\$(set +o)" != "\$save" __IN__ } test_long_option_default_off "$LINENO" allexport test_long_option_default_off "$LINENO" braceexpand test_long_option_default_on "$LINENO" caseglob test_long_option_default_on "$LINENO" clobber test_long_option_default_on "$LINENO" curasync test_long_option_default_on "$LINENO" curbg test_long_option_default_on "$LINENO" curstop test_long_option_default_off "$LINENO" dotglob test_long_option_default_off "$LINENO" emptylastfield test_long_option_default_off "$LINENO" errexit # One can never reset the no-exec option #test_long_option_default_on "$LINENO" exec test_long_option_default_off "$LINENO" extendedglob test_long_option_default_on "$LINENO" glob test_long_option_default_off "$LINENO" hashondef test_long_option_default_off "$LINENO" ignoreeof test_long_option_default_off "$LINENO" markdirs # The monitor option cannot be tested here due to dependency on the terminal. test_long_option_default_off "$LINENO" notify test_long_option_default_off "$LINENO" nullglob test_long_option_default_off "$LINENO" pipefail # This needs a special test (see below) #test_long_option_default_off "$LINENO" posixlycorrect test_long_option_default_on "$LINENO" traceall test_long_option_default_on "$LINENO" unset test_long_option_default_off "$LINENO" verbose test_long_option_default_off "$LINENO" xtrace ( if ! testee --version --verbose | grep -Fqx ' * history'; then skip="true" fi test_long_option_default_off "$LINENO" histspace ) ( if ! testee --version --verbose | grep -Fqx ' * lineedit'; then skip="true" fi test_long_option_default_off "$LINENO" emacs test_long_option_default_off "$LINENO" lealwaysrp test_long_option_default_off "$LINENO" lecompdebug test_long_option_default_off "$LINENO" leconvmeta test_long_option_default_off "$LINENO" lenoconvmeta test_long_option_default_on "$LINENO" lepromptsp test_long_option_default_off "$LINENO" levisiblebell test_long_option_default_off "$LINENO" notifyle test_long_option_default_off "$LINENO" vi ) test_x -e 0 'the posixlycorrect option is off by default' save="$(set +o)" && set +o posixlycorrect && test "$(set +o)" = "$save" && set -o posixlycorrect && test "$(set +o)" != "$save" && set -o noposixlycorrect && test "$(set +o)" = "$save" && set +o noposixlycorrect && test "$(set +o)" != "$save" && set +o posixlycorrect && set --posixlycorrect && test "$(set +o)" != "$save" && set +o posixlycorrect && test "$(set +o)" = "$save" && set ++noposixlycorrect && test "$(set +o)" != "$save" __IN__ # $1 = $LINENO, $2 = short option, $3 = long option test_unenablable_short_option() { testcase "$1" -e 2 "$3 cannot be enabled by set (short)" \ 3<<__IN__ 4&1 | head -n 1 __IN__ set: option `--cu' is ambiguous __OUT__ #' #` test_O -d -e 2 'ambiguous option (with and without "no"-prefix)' set --not __IN__ test_O -d -e 1 'printing to closed stream' set >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/settty-y.tst000066400000000000000000000007701354143602500161230ustar00rootroot00000000000000# settty-y.tst: yash-specific test of the set built-in ../checkfg || skip="true" # %REQUIRETTY% test_x -e 0 'monitor (short) on: $-' set -m && printf '%s\n' "$-" | grep -q m __IN__ test_x -e 0 'monitor (short) off: $-' -m set +m && printf '%s\n' "$-" | grep -qv m __IN__ test_x -e 0 'monitor (long) on: $-' set -o monitor && printf '%s\n' "$-" | grep -q m __IN__ test_x -e 0 'monitor (long) off: $-' -m set +o monitor && printf '%s\n' "$-" | grep -qv m __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/shift-p.tst000066400000000000000000000027431354143602500156750ustar00rootroot00000000000000# shift-p.tst: test of the shift built-in for any POSIX-compliant shell posix="true" setup -d test_oE -e 0 'shift 0 -> 0' -es shift 0 && bracket "$#" "$@" __IN__ [0] __OUT__ test_oE -e 0 'shift 1 -> 1' -es a shift 0 && bracket "$#" "$@" __IN__ [1][a] __OUT__ test_oE -e 0 'shift 1 -> 0' -es a shift 1 && bracket "$#" "$@" __IN__ [0] __OUT__ test_oE -e 0 'shift 2 -> 2' -es a 'b b' shift 0 && bracket "$#" "$@" __IN__ [2][a][b b] __OUT__ test_oE -e 0 'shift 2 -> 1' -es a 'b b' shift 1 && bracket "$#" "$@" __IN__ [1][b b] __OUT__ test_oE -e 0 'shift 2 -> 0' -es a 'b b' shift 2 && bracket "$#" "$@" __IN__ [0] __OUT__ test_oE -e 0 'shift 10 -> 3' -es a 'b b' c d e f g '' - j shift 7 && bracket "$#" "$@" __IN__ [3][][-][j] __OUT__ test_O -d -e n 'too large operand 1 for 0' -es shift 1 __IN__ test_O -d -e n 'too large operand 2 for 1' -es a shift 2 __IN__ test_O -d -e n 'too large operand 3 for 2' -es a 'b b' shift 3 __IN__ test_O -d -e n 'too large operand 100 for 10' -es a 'b b' c d e f g '' - j shift 100 __IN__ test_oE -e 0 'default operand is 1: success' -es a 'b b' c shift && bracket "$#" "$@" shift && bracket "$#" "$@" shift && bracket "$#" "$@" __IN__ [2][b b][c] [1][c] [0] __OUT__ test_O -d -e n 'default operand is 1: failure' -es shift __IN__ test_oE -e 0 'arguments are shifted in function' -es a 'b b' c func() { shift; bracket "$#" "$@"; } func x 'y y' z bracket "$#" "$@" __IN__ [2][y y][z] [3][a][b b][c] __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/shift-y.tst000066400000000000000000000046371354143602500157120ustar00rootroot00000000000000# shift-y.tst: yash-specific test of the shift built-in setup -d ( posix="true" test_Oe -e 2 'negative operand (POSIX)' shift -- -1 __IN__ shift: -1: the operand value must not be negative __ERR__ ) test_oE -e 0 'negative operand (non-POSIX) 1 -> 0' -es a shift -1 && bracket "$#" "$@" __IN__ [0] __OUT__ test_oE -e 0 'negative operand (non-POSIX) 2 -> 1' -es 'a a' b shift -1 && bracket "$#" "$@" __IN__ [1][a a] __OUT__ test_oE -e 0 'negative operand (non-POSIX) 2 -> 0' -es 'a a' b shift -2 && bracket "$#" "$@" __IN__ [0] __OUT__ test_oE -e 0 'array shift 0 -> 0' -e a=() shift -A a 0 && bracket "${a[#]}" "$a" __IN__ [0] __OUT__ test_oE -e 0 'array shift 10 -> 3' -e foo=(a 'b b' c d e f g '' - j) shift --array=foo -- 7 && bracket "${foo[#]}" "$foo" __IN__ [3][][-][j] __OUT__ test_o 'positional parameters are not modified on error' -s a 'b b' c shift 4 bracket "$#" "$@" __IN__ [3][a][b b][c] __OUT__ test_o 'array elements are not modified on error' a=(a 'b b' c) shift -A a 4 bracket "${a[#]}" "$a" __IN__ [3][a][b b][c] __OUT__ test_O -d -e 1 'too small operand -1 for 0' shift -- -1 __IN__ test_O -d -e 1 'too small operand -2 for 1' -s a shift -- -2 __IN__ test_O -d -e 1 'too large operand 2 for 1 array element' a=(X) shift -A a 2 __IN__ test_O -d -e 1 'too large operand 4 for 2 array element' a=(X Y) shift -A a 4 __IN__ test_Oe -e 2 'too many operands (against positional parameters)' shift 1 2 __IN__ shift: too many operands are specified __ERR__ test_Oe -e 2 'too many operands (against array)' a=(X Y) shift -A a 1 2 __IN__ shift: too many operands are specified __ERR__ test_Oe -e 2 'invalid option -z' shift -z __IN__ shift: `-z' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' shift --no-such=option __IN__ shift: `--no-such=option' is not a valid option __ERR__ #' #` test_Oe -e 2 'missing -A option argument' shift -A __IN__ shift: the -A option requires an argument __ERR__ test_O -d -e 2 'invalid operand (non-numeric)' shift a __IN__ test_O -d -e 2 'invalid operand (non-integral)' -s 1 shift 1.0 __IN__ test_Oe -e 1 'invalid array name (containing equal)' shift -A a=a 0 __IN__ shift: $a=a is not an array __ERR__ test_Oe -e 1 'invalid array name (non-existing variable)' shift -A a 0 __IN__ shift: $a is not an array __ERR__ test_Oe -e 1 'invalid array name (scalar variable)' a= shift -A a 0 __IN__ shift: $a is not an array __ERR__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/signal-p.tst000066400000000000000000000020461354143602500160310ustar00rootroot00000000000000# signal-p.tst: test of signal handling for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% posix="true" # $1 = line no. # $2 = signal name test_interactive_shell_signal_ignore() { testcase "$1" "interactive shell ignores SIG$2" -i 3<<__IN__ 4<<\__OUT__ kill -s $2 \$\$ echo - __IN__ - __OUT__ } test_interactive_shell_signal_ignore "$LINENO" INT test_interactive_shell_signal_ignore "$LINENO" QUIT test_interactive_shell_signal_ignore "$LINENO" TERM # $1 = line no. # $2 = signal name test_job_controlling_shell_signal_ignore() { testcase "$1" "job-controlling shell ignores SIG$2" -im \ 3<<__IN__ 4<<\__OUT__ kill -s $2 \$\$ echo - __IN__ - __OUT__ } test_job_controlling_shell_signal_ignore "$LINENO" TTIN test_job_controlling_shell_signal_ignore "$LINENO" TTOU test_job_controlling_shell_signal_ignore "$LINENO" TSTP test_oE 'traps are not handled until foreground job finishes' trap 'echo trapped' USR1 ( kill -s USR1 $$ echo signal sent ) __IN__ signal sent trapped __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/signal1-y.tst000066400000000000000000000051111354143602500161170ustar00rootroot00000000000000# signal1-y.tst: yash-specific test of signal handling, part 1 ../checkfg || skip="true" # %REQUIRETTY% cat >eraseps <<\__END__ PS1= PS2= __END__ # $1 = line no. # $2 = signal name test_interactive_subshell_signal_ignore() { testcase "$1" "SIG$2 kills interactive shell's subshell" -i \ 3<<__IN__ 4<<\__OUT__ ("$TESTEE" -c 'kill -s $2 \$PPID'; echo not reached) echo - __IN__ - __OUT__ } test_interactive_subshell_signal_ignore "$LINENO" INT test_interactive_subshell_signal_ignore "$LINENO" QUIT test_interactive_subshell_signal_ignore "$LINENO" TERM # $1 = line no. # $2 = signal name test_job_controlling_subshell_signal_ignore() { testcase "$1" "SIG$2 stops job-controlling shell's subshell" -im \ 3<<__IN__ 4<<\__OUT__ ("$TESTEE" -c 'kill -s $2 \$PPID'; echo resumed) echo - fg >/dev/null __IN__ - resumed __OUT__ } ( if "$use_valgrind"; then skip="true" fi test_job_controlling_subshell_signal_ignore "$LINENO" TTIN test_job_controlling_subshell_signal_ignore "$LINENO" TTOU test_job_controlling_subshell_signal_ignore "$LINENO" TSTP ) test_oe 'SIGINT interrupts interactive shell (+m)' -i +m --rcfile=./eraseps for i in 1 2 3; do echo $i "$TESTEE" -c 'kill -s INT $$' echo not reached done echo - >&2 for i in 4 5 6; do echo $i kill -s INT $$ echo not reached done echo done __IN__ 1 4 done __OUT__ - __ERR__ test_oe 'SIGINT interrupts interactive shell (-m)' -im --rcfile=./eraseps for i in 1 2 3; do echo $i "$TESTEE" -c 'kill -s INT $$' echo not reached done echo - >&2 for i in 4 5 6; do echo $i kill -s INT $$ echo not reached done echo done __IN__ 1 4 done __OUT__ - __ERR__ test_oE -e 0 'SIGINT spares asynchronous list (-i +m)' \ -i +m --rcfile=./eraseps "$TESTEE" -c 'kill -s INT $$; echo ok' & wait $! __IN__ ok __OUT__ test_oE -e 0 'SIGQUIT spares asynchronous list (-i +m)' \ -i +m --rcfile=./eraseps "$TESTEE" -c 'kill -s QUIT $$; echo ok' & wait $! __IN__ ok __OUT__ test_oE 'SIGTERM kills asynchronous list (-i +m)' \ -i +m --rcfile=./eraseps "$TESTEE" -c 'kill -s TERM $$; echo not reached' & wait $! kill -l $? __IN__ TERM __OUT__ test_oE 'SIGINT kills job-controlled asynchronous list' -m "$TESTEE" -c 'kill -s INT $$; echo not reached' & wait $! kill -l $? __IN__ INT __OUT__ test_oE 'SIGQUIT kills job-controlled asynchronous list' -m "$TESTEE" -c 'kill -s QUIT $$; echo not reached' & wait $! kill -l $? __IN__ QUIT __OUT__ test_oE 'SIGTERM kills job-controlled asynchronous list' -m "$TESTEE" -c 'kill -s TERM $$; echo not reached' & wait $! kill -l $? __IN__ TERM __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/signal2-y.tst000066400000000000000000000044661354143602500161340ustar00rootroot00000000000000# signal2-y.tst: yash-specific test of signal handling, part 2 ../checkfg || skip="true" # %REQUIRETTY% # $1 = line no. # $2 = signal name test_interactive_job_controlling_shell_job_signal_kill() { testcase "$1" "SIG$2 kills interactive job-controlling shell's job" \ -im 3<<__IN__ 4<<__OUT__ (kill -s $2 0) kill -l \$? __IN__ $2 __OUT__ } test_interactive_job_controlling_shell_job_signal_kill "$LINENO" ABRT test_interactive_job_controlling_shell_job_signal_kill "$LINENO" ALRM test_interactive_job_controlling_shell_job_signal_kill "$LINENO" BUS test_interactive_job_controlling_shell_job_signal_kill "$LINENO" FPE test_interactive_job_controlling_shell_job_signal_kill "$LINENO" HUP test_interactive_job_controlling_shell_job_signal_kill "$LINENO" ILL test_interactive_job_controlling_shell_job_signal_kill "$LINENO" INT test_interactive_job_controlling_shell_job_signal_kill "$LINENO" KILL test_interactive_job_controlling_shell_job_signal_kill "$LINENO" PIPE test_interactive_job_controlling_shell_job_signal_kill "$LINENO" QUIT test_interactive_job_controlling_shell_job_signal_kill "$LINENO" SEGV test_interactive_job_controlling_shell_job_signal_kill "$LINENO" TERM test_interactive_job_controlling_shell_job_signal_kill "$LINENO" USR1 test_interactive_job_controlling_shell_job_signal_kill "$LINENO" USR2 # $1 = line no. # $2 = signal name test_interactive_job_controlling_shell_job_signal_ignore() { testcase "$1" -e 0 \ "SIG$2 spares interactive job-controlling shell's job" \ -im 3<<__IN__ 4/dev/null __IN__ $2 continued __OUT__ } ( if "$use_valgrind"; then skip="true" fi test_interactive_job_controlling_shell_job_signal_stop "$LINENO" TSTP test_interactive_job_controlling_shell_job_signal_stop "$LINENO" TTIN test_interactive_job_controlling_shell_job_signal_stop "$LINENO" TTOU test_interactive_job_controlling_shell_job_signal_stop "$LINENO" STOP ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/signal3-y.tst000066400000000000000000000033211354143602500161220ustar00rootroot00000000000000# signal3-y.tst: yash-specific test of signal handling, part 3 ../checkfg || skip="true" # %REQUIRETTY% # $1 = line no. # $2 = signal name test_noninteractive_job_controlling_shell_signal_kill() { testcase "$1" -e "$2" "SIG$2 kills non-interactive job-controlling shell" \ -m +i 3<<__IN__ 4eraseps <<\__END__ PS1= PS2= __END__ # $1 = line no. # $2 = signal name test_interactive_job_controlling_shell_signal_kill() { testcase "$1" -e "$2" "SIG$2 kills interactive job-controlling shell" \ -im --rcfile=eraseps 3<<__IN__ 4/dev/null __IN__ $2 continued __OUT__ } ( if "$use_valgrind"; then skip="true" fi test_noninteractive_job_controlling_shell_job_signal_stop "$LINENO" TSTP test_noninteractive_job_controlling_shell_job_signal_stop "$LINENO" TTIN test_noninteractive_job_controlling_shell_job_signal_stop "$LINENO" TTOU test_noninteractive_job_controlling_shell_job_signal_stop "$LINENO" STOP ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/signal6-y.tst000066400000000000000000000033751354143602500161360ustar00rootroot00000000000000# signal6-y.tst: yash-specific test of signal handling, part 6 # $1 = line no. # $2 = signal name test_noninteractive_non_job_controlling_shell_signal_kill() { testcase "$1" -e "$2" \ "SIG$2 kills non-interactive non-job-controlling shell" \ +im 3<<__IN__ 4eraseps <<\__END__ PS1= PS2= __END__ # $1 = line no. # $2 = signal name test_interactive_non_job_controlling_shell_signal_kill() { testcase "$1" -e "$2" "SIG$2 kills interactive non-job-controlling shell" \ -i +m --rcfile=eraseps 3<<__IN__ 4|f1) 3>|f1 echo "$(test -f f1 || echo file does not exist $a)" __IN__ file does not exist __OUT__ test_OE -e 0 'redirections precedes assignments for non-special-builtin command' rm -f f2 a=$(cat f2) 3>|$(echo f2) true __IN__ test_oE 'single assignment' a= bracket "$a" a=value bracket "$a" __IN__ [] [value] __OUT__ test_oE 'multiple assignments' a= b=bar c=$b d=X e=$a bracket "$c" "$d" "$e" __IN__ [bar][X][] __OUT__ test_oE 'assignments are subject to expansion' x=X a=$x${x} b=$(echo $x)`echo $x` c=$((1+2)) bracket "$a" "$b" "$c" __IN__ [XX][XX][3] __OUT__ # Tilde expansion is tested in tilde-p.tst test_oE 'quotes in assignment' a='A"B"C' b="A'B'C" c=\'\"C\"\' bracket "$a" "$b" "$c" __IN__ [A"B"C][A'B'C]['"C"'] __OUT__ test_oE 'assignment is persistent for empty command' unset a b x a=A b=B $x bracket "$a" "$b" __IN__ [A][B] __OUT__ # Tested in builtins-p.tst #test_oE 'assignment is persistent for special built-in' test_oE 'assigned variable is visible inside function' f() { echo function $a; } a=1 a=2 f __IN__ function 2 __OUT__ test_oE 'assignment is temporary for regular command' a=1 a=2 echo ok bracket "$a" __IN__ ok [1] __OUT__ test_oE 'assignment is exported for regular command' a=A sh -c 'echo $a' __IN__ A __OUT__ test_O -d -e n 'assigning to read-only variable: exit with message (empty)' readonly a=A a=B echo not reached __IN__ test_O -d -e n 'assigning to read-only variable: exit with message (function)' func() { echo not reached function; } readonly a=A a=B func echo not reached command __IN__ test_O -d -e n 'assigning to read-only variable in subshell' readonly a=A (a=B) __IN__ test_x -e 0 'exit status of successful assignment' a=1 __IN__ test_x -e 0 'exit status of successful redirection' >/dev/null __IN__ test_x -e 0 'exit status of successful assignments and redirections' a=1 b=2 /dev/null __IN__ test_x -e 13 'exit status of assignment with command substitution' a=$(exit 13) __IN__ test_o 'assignment is done even if command substitution fails (+e)' +e a=foo$(false) bracket "$a" __IN__ [foo] __OUT__ test_o 'assignment is done even if command substitution fails (-e)' -e trap 'bracket "$a"' EXIT a=foo$(false) echo not reached __IN__ [foo] __OUT__ test_x -e 17 'exit status of redirection with command substitution' >/dev/null$(exit 17) __IN__ test_x -e 0 'redirection is done even if command substitution fails (+e)' +e >f11$(false) [ -f f11 ] __IN__ test_o 'redirection is done even if command substitution fails (-e)' -e trap '[ -f f12 ] && echo f12 created' EXIT >f12$(false) __IN__ f12 created __OUT__ test_o 'assignment-like command argument' export foo=F sh -c 'echo $1 $foo' X foo=bar foo=f sh -c 'echo $1 $foo' X foo=bar __IN__ foo=bar F foo=bar f __OUT__ test_o 'redirection can appear between any tokens in simple command' dir2/ext_cmd <<\END echo external echo command printf '[%s]\n' "$@" END chmod a+x dir2/ext_cmd ln -s "$(command -v sh)" dir2/link_to_sh test_o 'searching PATH for command' PATH=./dir1:./dir2:./dir3:$PATH ext_cmd argument ' 1 2 ' __IN__ external command [argument] [ 1 2 ] __OUT__ test_O -d -e 127 'command not found in PATH' PATH=./dir3 ext_cmd __IN__ test_o 'command name with slash' dir2/ext_cmd foo bar baz __IN__ external command [foo] [bar] [baz] __OUT__ ( # Ensure $PWD is safe to assign to $PATH case $PWD in (*[:%]*) skip="true" esac setup - <<\__END__ mkdir "$TEST_NO.path" && cd "$TEST_NO.path" make_command() for c do echo echo "Running $c" >"$c" && chmod a+x "$c"; done __END__ export TEST_NO="$LINENO" test_oE 'running command in different directory with relative path in $PATH' mkdir a b make_command a/command1 b/command1 PATH=.:$PATH cd a command1 cd ../b command1 __IN__ Running a/command1 Running b/command1 __OUT__ export TEST_NO="$LINENO" test_oE 'assignment to $PATH removes all remembered command paths' mkdir a b c PATH=$PWD/a:$PWD/b:$PWD/c:$PATH make_command c/command1 c/command2 command1 command2 echo --- make_command b/command1 b/command2 PATH="$PATH" make_command a/command1 a/command2 command1 command2 __IN__ Running c/command1 Running c/command2 --- Running a/command1 Running a/command2 __OUT__ export TEST_NO="$LINENO" test_oE 'remembered command path is ignored if command is missing' mkdir a b PATH=$PWD/a:$PWD/b:$PATH make_command b/command1 b/command2 command1 command2 echo --- make_command a/command1 a/command2; rm b/command1 b/command2 command1 command2 __IN__ Running b/command1 Running b/command2 --- Running a/command1 Running a/command2 __OUT__ ) test_o 'argv[0] (command name without slash)' sh -c 'echo "$0"' PATH=./dir2:$PATH link_to_sh -c 'echo "$0"' __IN__ sh link_to_sh __OUT__ testcase "$LINENO" 'argv[0] (command name with slash)' 3<<\__IN__ 4<<__OUT__ "$(command -v sh)" -c 'echo "$0"' ./dir2/link_to_sh -c 'echo "$0"' __IN__ $(command -v sh) ./dir2/link_to_sh __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/simple-y.tst000066400000000000000000000046551354143602500160660ustar00rootroot00000000000000# simple-y.tst: yash-specific test of simple commands setup -d test_oE 'words are expanded in order of appearance' a=1-2-3 IFS= bracket $a ${IFS:= -} $a __IN__ [1-2-3][1][2][3] __OUT__ test_oE 'assignment is exported during and after special built-in execution' a=1 eval 'sh -c "echo \$a"' sh -c "echo \$a" __IN__ 1 1 __OUT__ test_oE 'assignment does not persist after function returns' f() { :; } a=1 a=2 f echo $a __IN__ 1 __OUT__ test_oE 'assignment is exported during function execution' f() { sh -c 'echo $a'; } a=1 f __IN__ 1 __OUT__ test_o -d 'COMMAND_NOT_FOUND_HANDLER is run when command was not found' COMMAND_NOT_FOUND_HANDLER=('echo not found' 'echo handled') ./_no_such_command_ __IN__ not found handled __OUT__ test_o -d 'COMMAND_NOT_FOUND_HANDLER assignment and command in single command' COMMAND_NOT_FOUND_HANDLER=('echo not found' 'echo handled') \ ./_no_such_command_ __IN__ not found handled __OUT__ test_o 'positional parameters in not-found handler' set -- positional parameters COMMAND_NOT_FOUND_HANDLER='bracket ! "$@"; set --' ./_no_such_command_ not found 'command arguments' echo "$@" __IN__ [!][./_no_such_command_][not][found][command arguments] positional parameters __OUT__ test_o 'local variables in not-found handler' i=out COMMAND_NOT_FOUND_HANDLER=('typeset i=in' 'echo $i') ./_no_such_command_ echo $i __IN__ in out __OUT__ test_o 'local variable HANDLED is defined empty in not-found handler' COMMAND_NOT_FOUND_HANDLER=('bracket "${HANDLED-unset}"') ./_no_such_command_ bracket "${HANDLED-unset}" readonly HANDLED=dummy COMMAND_NOT_FOUND_HANDLER=('bracket "${HANDLED-unset}"') ./_no_such_command_ bracket "${HANDLED-unset}" __IN__ [] [unset] [] [dummy] __OUT__ test_x -e 127 'exit status of not-found command (HANDLED unset)' COMMAND_NOT_FOUND_HANDLER=('unset HANDLED') ./_no_such_command_ __IN__ test_x -e 127 'exit status of not-found command (HANDLED empty)' COMMAND_NOT_FOUND_HANDLER=('') ./_no_such_command_ __IN__ test_x -e 29 'exit status of not-found command (HANDLED non-empty)' COMMAND_NOT_FOUND_HANDLER=('HANDLED=X' '(exit 29)') ./_no_such_command_ __IN__ test_o 'not-found handler is not run recursively' COMMAND_NOT_FOUND_HANDLER=('echo in' ./_no_such_command_ 'echo out') ./_no_such_command_ __IN__ in out __OUT__ ( posix=true test_O -e 127 'not-found handler is not run in POSIXly-correct mode' COMMAND_NOT_FOUND_HANDLER='echo not reached' ./_no_such_command_ __IN__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/startup-y.tst000066400000000000000000000312641354143602500162730ustar00rootroot00000000000000# startup-y.tst: yash-specific test of shell startup test_O -e 17 'one operand with -c' -c 'exit 17' __IN__ test_o -e 0 'two operands with -c' \ -c 'printf "[%s]\n" "$0" "$@"' 'command name' __IN__ [command name] __OUT__ test_o -e 0 'one positional parameter with -c' \ -c 'printf "[%s]\n" "$0" "$@"' 0 1 __IN__ [0] [1] __OUT__ test_o -e 0 'many positional parameters with -c' \ -c 'printf "[%s]\n" "$0" "$@"' 0 1 '2 2' 3 4 - 6 7 8 9 10 11 __IN__ [0] [1] [2 2] [3] [4] [-] [6] [7] [8] [9] [10] [11] __OUT__ test_oE -e 0 'stdin is not used with -c' "$TESTEE" -c 'echo foo' <&- __IN__ foo __OUT__ test_oE -e 19 'no operands with -s' -s echo $# exit 19 __IN__ 0 __OUT__ test_oE -e 23 'one operand with -s' -s '1 1' printf "[%s]\n" "$@" exit 23 __IN__ [1 1] __OUT__ test_oE 'two operands with -s' -s '1 1' 2 printf "[%s]\n" "$@" __IN__ [1 1] [2] __OUT__ test_oE 'many operands with -s' -s '1 1' 2 3 4 - 6 7 8 9 10 11 printf "[%s]\n" "$@" __IN__ [1 1] [2] [3] [4] [-] [6] [7] [8] [9] [10] [11] __OUT__ test_oE '$0 with -s' exec -a '0 0' "$TESTEE" -s <<\__END__ printf '[%s]\n' "$0" __END__ __IN__ [0 0] __OUT__ test_oE -e 0 'negating -c and enabling -s' -c +c -s echo ok __IN__ ok __OUT__ test_oE -e 0 'negating -s and enabling -c' -s +s -c 'echo ok' __IN__ ok __OUT__ testcase "$LINENO" -e 2 'missing command with -c' -c \ 3"$input" <<\__END__ echo input "$*" cat exit 3 echo not reached __END__ test_oE -e 3 'reading file w/o positional parameters' "$input" stdin __IN__ input stdin __OUT__ test_oE -e 3 'reading file with one positional parameter' "$input" '1 1' stdin __IN__ input 1 1 stdin __OUT__ test_oE -e 3 'reading file with many positional parameters' \ "$input" '1 1' 2 3 4 - 6 7 8 9 10 11 stdin __IN__ input 1 1 2 3 4 - 6 7 8 9 10 11 stdin __OUT__ ) ( input=input$LINENO >"$input" chmod a-r "$input" # Skip if we're root. if { <"$input"; } 2>/dev/null; then skip="true" fi test_O -d -e n 'reading non-readable file' "$input" __IN__ ) test_O -d -e 127 'reading non-existing file' ./_no_such_file_ __IN__ ( unset YASH_LOADPATH test_o 'LOADPATH is set to default if missing' echo ${YASH_LOADPATH:+set} __IN__ set __OUT__ ) ( export YASH_LOADPATH=/foo/bar:/baz test_o 'LOADPATH is not modified if exists in environment' echo ${YASH_LOADPATH:-unset} __IN__ /foo/bar:/baz __OUT__ ) ( export HOME="${PWD%/}/home$LINENO" mkdir "$HOME" echo echo profile >"$HOME/.yash_profile" echo echo yashrc >"$HOME/.yashrc" test_oE 'startup: no argument' echo $- __IN__ s __OUT__ test_oE 'startup: -c' -c 'echo $-' __IN__ c __OUT__ test_oE 'startup: -cl, short option, with profile' -cl 'echo $-' __IN__ profile cl __OUT__ test_oE 'startup: -cl, long option, with profile' --cmdline --log-in 'echo $-' __IN__ profile cl __OUT__ test_oE 'startup: -ci +m, short option, with rcfile' -ci +m 'echo $-' __IN__ yashrc ci __OUT__ test_oE 'startup: -ci +m, long option, with rcfile' \ --cmdline --interactive --no-monitor 'echo $-' __IN__ yashrc ci __OUT__ test_oE 'startup: -cil +m, short option, with profile/rcfile' -cil +m 'echo $-' __IN__ profile yashrc cil __OUT__ test_oE 'startup: -cil +m, long option, with profile/rcfile' \ --cmdline --interactive --log-in --no-monitor 'echo $-' __IN__ profile yashrc cil __OUT__ test_oE 'startup: -cil +m --noprofile' -cil +m --noprofile 'echo $-' __IN__ yashrc cil __OUT__ test_oE 'startup: -cil +m --norcfile' -cil +m --norcfile 'echo $-' __IN__ profile cil __OUT__ ) test_oE 'startup: -cl with unset HOME' -cl 'echo $-' __IN__ cl __OUT__ test_oE 'startup: -ci +m with unset HOME' -ci +m 'echo $-' __IN__ ci __OUT__ ( export HOME="${PWD%/}/_no_such_directory_" test_oE 'startup: -cl with non-existing HOME' -cl 'echo $-' __IN__ cl __OUT__ test_oE 'startup: -ci +m with non-existing HOME' -ci +m 'echo $-' __IN__ ci __OUT__ ) ( profile="profile$LINENO" rcfile="rcfile$LINENO" echo echo local profile >"$profile" echo echo local rcfile >"$rcfile" test_oE 'startup: -cl, specified profile' -cl --profile="$profile" 'echo $-' __IN__ local profile cl __OUT__ test_oE 'startup: -ci +m, specified rcfile' -ci +m --rcfile="$rcfile" 'echo $-' __IN__ local rcfile ci __OUT__ test_oE 'startup: -cil +m, specified rcfile' \ -cil +m --profile="$profile" --rcfile="$rcfile" 'echo $-' __IN__ local profile local rcfile cil __OUT__ ) ( # Ensure $PWD is safe to assign to $YASH_LOADPATH case $PWD in (*[:%]*) skip="true" esac export HOME="${PWD%/}/home$LINENO" export YASH_LOADPATH="$HOME/loadpath" export ENV='${PWD%/}/_no_such_file_' mkdir -p "$HOME/loadpath/initialization" echo echo default >"$HOME/loadpath/initialization/default" test_oE 'startup: -ci +m, LOADPATH fallback for missing yashrc' \ -ci +m 'echo $-' __IN__ default ci __OUT__ test_oE 'startup: -ci +m, no LOADPATH fallback in POSIX mode' \ --posix -ci +m 'echo $-' __IN__ ci __OUT__ test_oE 'startup: -ci +m, no LOADPATH fallback with specified rcfile' \ -ci +m --rcfile=_no_such_file_ 'echo $-' __IN__ ci __OUT__ echo echo yashrc >"$HOME/.yashrc" test_oE 'startup: -ci +m, no LOADPATH fallback if ~/.yashrc found' \ -ci +m 'echo $-' __IN__ yashrc ci __OUT__ ) ( export HOME="${PWD%/}/home$LINENO" mkdir "$HOME" cat >"$HOME/.yash_profile" <<\__END__ echo error 1 . "$HOME/profile2" echo error 1 syntax error \$\?=$? unset var echo ${var?} echo error 1 expansion error \$\?=$? fi echo not reached __END__ cat >"$HOME/profile2" <<\__END__ echo error 2 unset var echo ${var?} echo error 2 expansion error \$\?=$? fi echo not reached __END__ ln -s .yash_profile "$HOME/.yashrc" test_o -d -e 0 'errors in profile' -cl 'echo $-' __IN__ error 1 error 2 error 2 expansion error $?=2 error 1 syntax error $?=258 error 1 expansion error $?=2 cl __OUT__ test_o -d -e 0 'errors in rcfile' -ci +m 'echo $-' __IN__ error 1 error 2 error 2 expansion error $?=2 error 1 syntax error $?=258 error 1 expansion error $?=2 ci __OUT__ ) test_o 'startup: -abCcefhluvx' -abCcefhluvx 'echo $-' __IN__ aCcefhlbuvx __OUT__ test_o 'startup: -abCefhlsuvx' -abCefhlsuvx echo $- __IN__ aCefhlbsuvx __OUT__ test_oE 'first operand is ignored if it is a hyphen (-c)' -c - 'echo $-' __IN__ c __OUT__ test_oE 'first operand is ignored if it is a hyphen (-s)' -s - -- 2 echo $- "$2" "$1" __IN__ s 2 -- __OUT__ test_oE 'first operand is ignored if it is a hyphen (no -c or -s)' - echo $- $# __IN__ s 0 __OUT__ ( echo echo env >env export ENV='${PWD%/}/env' test_oE 'startup: --posix -c' --posix -c 'echo $-' __IN__ c __OUT__ test_oE 'startup: --posix -ci +m' --posix -ci +m 'echo $-' __IN__ env ci __OUT__ ) test_oE 'startup: --posix -ci +m with unset ENV' --posix -ci +m 'echo $-' __IN__ ci __OUT__ ( export ENV='${PWD%/}/_no_such_file_' test_oE 'startup: --posix -ci +m with non-existing ENV' --posix -ci +m 'echo $-' __IN__ ci __OUT__ ) test_oE 'program name yash disables POSIX mode (w/o directory name)' exec -a yash "$TESTEE" <<\__END__ set +o | grep posixlycorrect __END__ __IN__ set +o posixlycorrect __OUT__ test_oE 'program name yash disables POSIX mode (with directory name)' exec -a /bin/yash "$TESTEE" <<\__END__ set +o | grep posixlycorrect __END__ __IN__ set +o posixlycorrect __OUT__ test_oE 'program name sh enables POSIX mode (w/o directory name)' exec -a sh "$TESTEE" <<\__END__ set +o | grep posixlycorrect __END__ __IN__ set -o posixlycorrect __OUT__ test_oE 'program name sh enables POSIX mode (with directory name)' exec -a /bin/sh "$TESTEE" <<\__END__ set +o | grep posixlycorrect __END__ __IN__ set -o posixlycorrect __OUT__ test_oE 'hyphen prefix enables interactive mode (w/o directory name)' exec -a -yash "$TESTEE" <<\__END__ echo $- __END__ __IN__ ls __OUT__ test_oE 'hyphen prefix enables interactive mode (with directory name)' exec -a -/bin/yash "$TESTEE" <<\__END__ echo $- __END__ __IN__ ls __OUT__ # We cannot test this without mocking the terminal. #test_oE 'interactive mode is enabled if stdin/stdout are terminal' # Tested in job-y.tst #test_oE 'job control is on by default in interactive shell' ( if ! testee --version --verbose | grep -Fqx ' * help' || ! testee --version --verbose | grep -Fqx ' * lineedit'; then skip="true" fi test_oE -e 0 'help' --help __IN__ Syntax: yash [option...] [filename [argument...]] yash [option...] -c command [command_name [argument...]] yash [option...] -s [argument...] Options: --help -V --version --noprofile --norcfile --profile=... --rcfile=... -a -o allexport -o braceexpand -o caseglob +C -o clobber -c -o cmdline -o curasync -o curbg -o curstop -o dotglob -o emacs -o emptylastfield -e -o errexit -o errreturn +n -o exec -o extendedglob -o forlocal +f -o glob -h -o hashondef -o histspace -o ignoreeof -i -o interactive -o lealwaysrp -o lecompdebug -o leconvmeta -o lenoconvmeta -o lepredict -o lepredictempty -o lepromptsp -o levisiblebell -o log -l -o login -o markdirs -m -o monitor -b -o notify -o notifyle -o nullglob -o pipefail -o posixlycorrect -s -o stdin -o traceall +u -o unset -v -o verbose -o vi -x -o xtrace Try `man yash' for details. __OUT__ #` # No long options in the POSIXly-correct mode test_oE -e 0 'help (POSIX)' --help --posixly-correct __IN__ Syntax: sh [option...] [filename [argument...]] sh [option...] -c command [command_name [argument...]] sh [option...] -s [argument...] Options: -a -o allexport -o braceexpand -o caseglob +C -o clobber -c -o cmdline -o curasync -o curbg -o curstop -o dotglob -o emacs -o emptylastfield -e -o errexit -o errreturn +n -o exec -o extendedglob -o forlocal +f -o glob -h -o hashondef -o histspace -o ignoreeof -i -o interactive -o lealwaysrp -o lecompdebug -o leconvmeta -o lenoconvmeta -o lepredict -o lepredictempty -o lepromptsp -o levisiblebell -o log -l -o login -o markdirs -m -o monitor -b -o notify -o notifyle -o nullglob -o pipefail -o posixlycorrect -s -o stdin -o traceall +u -o unset -v -o verbose -o vi -x -o xtrace Try `man yash' for details. __OUT__ #` ) test_E -e 0 'version' --version __IN__ test_E -e 0 'verbose version, short option' -Vv __IN__ test_E -e 0 'verbose version, long option' --version --verbose __IN__ testcase "$LINENO" -e 2 'version (short option in POSIX mode)' --posix -V \ 3. set -Ceu export LC_ALL=C uname -a date printf '=============\n\n' passed=0 failed=0 skipped=0 for result_file do # The "grep" command is generally faster than repeated "read" built-in. if [ "$(grep -cE '^%%% (FAIL|SKIPP)ED:' "$result_file")" -eq 0 ]; then passed="$((passed + $(grep -c '^%%% PASSED:' "$result_file" || true)))" continue fi log='' while IFS= read -r line; do log="$log $line" case $line in ('%%% START:'*) log="$line" ;; ('%%% PASSED:'*) passed="$((passed + 1))" ;; ('%%% FAILED:'*) printf '%s\n\n' "$log" failed="$((failed + 1))" ;; ('%%% SKIPPED:'*) printf '%s\n\n' "$line" skipped="$((skipped + 1))" ;; esac done <"$result_file" done printf '=============\n' printf 'TOTAL: %4d\n' "$((passed + failed + skipped))" printf 'PASSED: %4d\n' "$passed" printf 'FAILED: %4d\n' "$failed" printf 'SKIPPED: %4d\n' "$skipped" printf '=============\n' # vim: set ts=8 sts=4 sw=4 noet: yash-2.49/tests/suspend-y.tst000066400000000000000000000006741354143602500162530ustar00rootroot00000000000000# suspend-y.tst: yash-specific test of the suspend built-in test_oE -e 0 'suspend is a semi-special built-in' command -V suspend __IN__ suspend: a semi-special built-in __OUT__ test_Oe -e 2 'too many operands' suspend foo __IN__ suspend: no operand is expected __ERR__ test_Oe -e 2 'invalid option --xxx' suspend --no-such=option __IN__ suspend: `--no-such=option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/test-p.tst000066400000000000000000000165561354143602500155460ustar00rootroot00000000000000# test-p.tst: test of the test built-in for any POSIX-compliant shell posix="true" umask u=rwx,go= >file (umask a-r && >unreadable) (umask a-w && >unwritable) (umask a-x && >unexecutable) >executable chmod u+x executable echo >oneline echo foo >nonempty mkdir dir setgroupid setuserid chmod ug-s dir chmod g+s setgroupid chmod u+s setuserid mkfifo fifo ln -s file filelink ln -s _no_such_file_ brokenlink ln -s unreadable unreadablelink ln -s unwritable unwritablelink ln -s unexecutable unexecutableln ln -s executable executablelink ln -s oneline onelinelink ln -s nonempty nonemptylink ln -s dir dirlink ln -s setgroupid setgroupidlink ln -s setuserid setuseridlink ln -s fifo fifolink # $1 = $LINENO, $2 = expected exit status, $3... = expression assert() ( setup <<\__END__ test "$@" result_test=$? [ "$@" ] result_bracket=$? case "$result_test" in ("$result_bracket") exit "$result_bracket" esac printf 'result_test=%d result_bracket=%d\n' "$result_test" "$result_bracket" exit 100 __END__ lineno="$1" expected_exit_status="$2" shift 2 testcase "$lineno" -e "$expected_exit_status" "test $*" -s -- "$@" \ 3/dev/null; then skip="true" fi . ../test-y.sh umask u=rwx,go= >file ln -s file filelink ln -s _no_such_file_ brokenlink ln file hardlink touch -t 200001010000 older touch -t 200101010000 newer touch -a -t 200101010000 old; touch -m -t 200001010000 old touch -a -t 200001010000 new; touch -m -t 200101010000 new ( mkdir dir sticky if ! { chmod a-t dir && chmod a+t sticky; } then skip="true" fi ln -s sticky stickylink assert_true -k assert_true -k sticky assert_true -k stickylink assert_false -k file assert_false -k filelink assert_false -k dir assert_false -k dirlink assert_false -k ./_no_such_file_ assert_false -k brokenlink ) assert_true -G # Tests for the -G operator is missing assert_false -G ./_no_such_file_ assert_false -G brokenlink assert_true -N assert_true -N new assert_false -N old assert_false -N ./_no_such_file_ assert_false -N brokenlink assert_true -O # Tests for the -O operator is missing assert_false -O ./_no_such_file_ assert_false -O brokenlink assert_true -o assert_false -o allexpo assert_false -o allexport assert_false -o all-_export assert_false -o allexportttttttt assert_true -o \?allexpo assert_true -o \?allexport assert_true -o \?all-_export assert_false -o \?allexportttttttt ( setup 'set -o allexport' assert_true -o allexpo assert_true -o allexport assert_true -o all-_export assert_false -o allexportttttttt assert_true -o \?allexpo assert_true -o \?allexport assert_true -o \?all-_export assert_false -o \?allexportttttttt ) assert_false -o tify assert_false -o notify assert_true -o nonotify assert_true -o n-o-n-otify assert_false -o \?tify assert_true -o \?notify assert_true -o \?nonotify assert_true -o \?n-o-n-otify ( setup 'set -o notify' assert_false -o tify assert_true -o notify assert_false -o nonotify assert_false -o n-o-n-otify assert_false -o \?tify assert_true -o \?notify assert_true -o \?nonotify assert_true -o \?n-o-n-otify ) assert_true XXXXX -ot newer assert_false XXXXX -ot XXXXX assert_false newer -ot XXXXX assert_true older -ot newer assert_false newer -ot newer assert_false newer -ot older assert_false XXXXX -nt newer assert_false XXXXX -nt XXXXX assert_true newer -nt XXXXX assert_false older -nt newer assert_false older -nt older assert_true newer -nt older assert_false XXXXX -ef newer assert_false XXXXX -ef XXXXX assert_false newer -ef XXXXX assert_false older -ef newer assert_true older -ef older assert_false newer -ef older assert_true file -ef hardlink assert_false file -ef newer assert_true 1 -a "(" 1 = 0 -o "(" 2 = 2 ")" ")" -a "(" = ")" assert_true -n = -o -o -n = -n # ( -n = -o ) -o ( -n = -n ) assert_true -n = -a -n = -n # ( -n = ) -a ( -n = -n ) test_Oe -e 2 'invalid binary operator' test 1 2 __IN__ test: `1' is not a unary operator __ERR__ #' #` test_Oe -e 2 'invalid binary operator' test 1 2 3 __IN__ test: `2' is not a binary operator __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/test2-y.tst000066400000000000000000000041501354143602500156240ustar00rootroot00000000000000# test2-y.tst: yash-specific test of the test built-in, part 2 if ! testee -c 'command -bv test' >/dev/null; then skip="true" fi . ../test-y.sh assert_true "" == "" assert_true 1 == 1 assert_true abcde == abcde assert_false 0 == 1 assert_false abcde == 12345 assert_true ! == ! assert_true == == == assert_false "(" == ")" # The behavior of the ===, !==, <, <=, >, >= operators cannot be fully tested. assert_true "" === "" assert_true 1 === 1 assert_true abcde === abcde assert_false 0 === 1 assert_false abcde === 12345 assert_true ! === ! assert_true === === === assert_false "(" === ")" assert_false "" !== "" assert_false 1 !== 1 assert_false abcde !== abcde assert_true 0 !== 1 assert_true abcde !== 12345 assert_false ! !== ! assert_false !== !== !== assert_true "(" !== ")" assert_false 11 '<' 100 assert_false 11 '<' 11 assert_true 100 '<' 11 assert_false 11 '<=' 100 assert_true 11 '<=' 11 assert_true 100 '<=' 11 assert_true 11 '>' 100 assert_false 11 '>' 11 assert_false 100 '>' 11 assert_true 11 '>=' 100 assert_true 11 '>=' 11 assert_false 100 '>=' 11 assert_true abc123xyz =~ 'c[[:digit:]]*x' assert_false -axyzxyzaxyz- =~ 'c[[:digit:]]*x' assert_true -axyzxyzaxyz- =~ '-(a|xyz)*-' assert_false abc123xyz =~ '-(a|xyz)*-' assert_true "" -veq "" assert_true 0 -veq 0 assert_false 0 -veq 1 assert_false 1 -veq 0 assert_true 01 -veq 0001 assert_true .%=01 -veq .%=0001 assert_true 0.01.. -veq 0.1.. assert_false 0.01.0 -veq 0.1. assert_false "" -vne "" assert_false 0 -vne 0 assert_true 0 -vne 1 assert_true 1 -vne 0 assert_false "" -vgt "" assert_false 0 -vgt 0 assert_false 0 -vgt 1 assert_true 1 -vgt 0 assert_true "" -vge "" assert_true 0 -vge 0 assert_false 0 -vge 1 assert_true 1 -vge 0 assert_false "" -vlt "" assert_false 0 -vlt 0 assert_true 0 -vlt 1 assert_false 1 -vlt 0 assert_false 0.01.0 -vlt 0.1.. assert_false 0.01.0 -vlt 0.1.: assert_true "" -vle "" assert_true 0 -vle 0 assert_true 0 -vle 1 assert_false 1 -vle 0 assert_true 02 -vle 0100 assert_true .%=02 -vle .%=0100 assert_false 0.01.0 -vle 0.1.a0 assert_true 1.2.3 -vle 1.3.2 assert_true -2 -vle -3 # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/testtty-p.tst000066400000000000000000000015641354143602500163000ustar00rootroot00000000000000# testtty-p.tst: test of the test built-in for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% if ! testee -c 'command -bv test' >/dev/null; then skip="true" fi posix="true" test_OE -e 1 'unary -t: empty operand' test -t '' __IN__ test_OE -e 1 'unary -t: non-numeric operand' test -t x __IN__ test_OE -e 1 'unary -t: negative operand' test -t -10 __IN__ test_OE -e 1 'unary -t: closed file descriptor 0' test -t 0 0>&- __IN__ test_OE -e 1 'unary -t: non-tty file descriptor 0' test -t 0 0/dev/tty __IN__ test_OE -e 1 'unary -t: closed file descriptor 5' test -t 5 5>&- __IN__ test_OE -e 1 'unary -t: non-tty file descriptor 5' test -t 5 5/dev/tty __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/tilde-p.tst000066400000000000000000000131201354143602500156500ustar00rootroot00000000000000# tilde-p.tst: test of tilde expansion for any POSIX-compliant shell posix="true" setup -d ( setup 'HOME=/foo/bar' test_oE 'quoted tilde is not expanded (in command word)' bracket \~ '~' "~" ~\/ __IN__ [~][~][~][~/] __OUT__ test_oE 'unnamed tilde expansion, end of word' bracket ~ HOME=/tilde/expansion bracket ~ __IN__ [/foo/bar] [/tilde/expansion] __OUT__ test_oE 'unnamed tilde expansion, followed by slash' bracket ~/ ~/baz HOME=/tilde/expansion bracket ~/ ~/slash __IN__ [/foo/bar/][/foo/bar/baz] [/tilde/expansion/][/tilde/expansion/slash] __OUT__ test_OE -e 0 'exit status of successful unnamed tilde expansion (in command word)' : ~ ~/ ~/foo __IN__ test_oE 'quoted tilde is not expanded in assignment' a=\~ b='~' c="~" d=~\/ bracket "$a" "$b" "$c" "$d" __IN__ [~][~][~][~/] __OUT__ test_oE 'unnamed tilde expansion in assignment, end of word' a=~ HOME=/tilde/expansion b=~ bracket "$a" "$b" __IN__ [/foo/bar][/tilde/expansion] __OUT__ test_oE 'unnamed tilde expansion in assignment, followed by slash' a=~/ b=~/baz HOME=/tilde/expansion c=~/ d=~/slash bracket "$a" "$b" "$c" "$d" __IN__ [/foo/bar/][/foo/bar/baz][/tilde/expansion/][/tilde/expansion/slash] __OUT__ test_oE 'unnamed tilde expansion in assignment, followed by colon' a=~: b=~:baz HOME=/tilde/expansion c=~: d=~:colon bracket "$a" "$b" "$c" "$d" __IN__ [/foo/bar:][/foo/bar:baz][/tilde/expansion:][/tilde/expansion:colon] __OUT__ test_oE -e 0 'unnamed tilde expansion in assignment, following colon' a=:~ b=baz:~ HOME=/tilde/expansion c=:~ d=colon:~ bracket "$a" "$b" "$c" "$d" __IN__ [:/foo/bar][baz:/foo/bar][:/tilde/expansion][colon:/tilde/expansion] __OUT__ test_oE -e 0 'unnamed tilde expansion in assignment, between colon' a=:~: b=baz:~:baz HOME=/tilde/expansion c=:~: d=colon:~:colon bracket "$a" "$b" "$c" "$d" __IN__ [:/foo/bar:][baz:/foo/bar:baz][:/tilde/expansion:][colon:/tilde/expansion:colon] __OUT__ test_oE -e 0 'many unnamed tilde expansions in assignment' a=~:x:~/y:~:~ bracket "$a" __IN__ [/foo/bar:x:/foo/bar/y:/foo/bar:/foo/bar] __OUT__ test_OE -e 0 'exit status of successful unnamed tilde expansion in assignment' a=~:x:~/y:~:~ __IN__ ) ( if ! { LOGNAME="$(logname)" && export LOGNAME; } then skip="true" elif # The current user's name has to be portable. printf '%s\n' "$LOGNAME" | \ grep -q '[^0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-]' then skip="true" elif # Tests of named tilde expansions depend on the home directory of the # current user. ! { HOME="$(eval printf \'%s\\\n\' \~$LOGNAME)" && export HOME && [ "$HOME" ]; } then skip="true" fi if "${skip:-false}"; then LOGNAME= HOME= fi testcase "$LINENO" 'tilde with quoted name is not expanded (in command word)' \ 3<<__IN__ 4<<__OUT__ 5/dev/null 2>&1; then skip="true" fi # The below test case should be skipped if the user "_no_such_user_" exists and # the shell has a permission to show its home directory. However, the above # test may fail to skip the test case if the "id" utility still don't have a # permission to show its attributes. I assume such a special case doesn't # actually happen. test_oE -e 0 'tilde expansion for unknown user' echoraw ~_no_such_user_ __IN__ ~_no_such_user_ __OUT__ ) test_oE '~+' PWD=/pwd echoraw ~+ __IN__ /pwd __OUT__ test_oE '~-' OLDPWD=/old-pwd echoraw ~- __IN__ /old-pwd __OUT__ ( if ! testee -c 'command -bv pushd' >/dev/null; then skip="true" fi test_oE -e 0 'tilde expansion for directory stack entry' PWD=/pwd unset DIRSTACK echoraw ~+0 ~-0 DIRSTACK=(/foo /bar/baz) echoraw ~+0 ~+1 ~+2 ~+3 echoraw ~-0 ~-1 ~-2 ~-3 __IN__ /pwd /pwd /pwd /bar/baz /foo ~+3 /foo /bar/baz /pwd ~-3 __OUT__ ) # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/times-y.tst000066400000000000000000000005761354143602500157140ustar00rootroot00000000000000# times-y.tst: yash-specific test of the times built-in test_Oe -e 2 'too many operands' times foo __IN__ times: no operand is expected __ERR__ test_Oe -e 2 'invalid option --xxx' times --no-such=option __IN__ times: `--no-such=option' is not a valid option __ERR__ #' #` test_O -d -e 1 'printing to closed output stream' times >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/trap-p.tst000066400000000000000000000123651354143602500155270ustar00rootroot00000000000000# trap-p.tst: test of the trap built-in for any POSIX-compliant shell posix="true" test_OE -e USR1 'setting default trap' trap - USR1 kill -s USR1 $$ __IN__ test_OE -e 0 'setting ignore trap' trap '' USR1 kill -s USR1 $$ (kill -s USR1 $$) __IN__ test_oE -e 0 'setting command trap' trap 'echo trap; echo executed' USR1 kill -s USR1 $$ __IN__ trap executed __OUT__ test_OE -e USR1 'resetting to default trap' trap '' USR1 trap - USR1 kill -s USR1 $$ __IN__ test_oE -e 0 'specifying multiple signals' trap 'echo trapped' USR1 USR2 kill -s USR1 $$ kill -s USR2 $$ __IN__ trapped trapped __OUT__ # $1 = $LINENO, $2 = signal number, $3 = signal name w/o SIG-prefix test_specifying_signal_by_number() { testcase "$1" -e 0 "specifying signal by number ($3)" \ 3<<__IN__ 4<<__OUT__ 5/dev/null' __IN__ foo __OUT__ test_oE 'trap command is not affected by redirections effective when set (2)' \ -c '{ trap "echo foo" EXIT; } >/dev/null' __IN__ foo __OUT__ test_oE 'trap command is not affected by redirections effective when set (3)' \ -c 'f() { eval "trap \"echo foo\" EXIT"; }; f >/dev/null' __IN__ foo __OUT__ test_oE 'command is evaluated each time trap is executed' trap X USR1 alias X='echo 1' kill -s USR1 $$ alias X='echo 2' kill -s USR1 $$ __IN__ 1 2 __OUT__ ( trap '' USR1 USR2 test_oE 'traps cannot be modified for initially ignored signal' trap - USR1 2>/dev/null trap 'echo trapped' USR2 2>/dev/null kill -s USR1 $$ # ignored kill -s USR2 $$ # ignored echo reached __IN__ reached __OUT__ ) test_oE -e 0 'single trap may be invoked more than once' trap 'echo trapped' USR1 kill -s USR1 $$ (kill -s USR1 $$) kill -s USR1 $$ __IN__ trapped trapped trapped __OUT__ test_OE -e 0 'ignore trap is inherited to external command' trap '' USR1 "$TESTEE" -c 'kill -s USR1 $$' __IN__ test_oE -e 0 'command trap is reset in external command' trap 'echo trapped' USR1 "$TESTEE" -c 'kill -s USR1 $$' kill -l $? __IN__ USR1 __OUT__ test_oE 'default traps remain in subshell' trap - USR1 ("$TESTEE" -c 'kill -s USR1 $$') kill -l $? __IN__ USR1 __OUT__ test_OE -e 0 'ignored traps remain in subshell' trap '' USR1 ("$TESTEE" -c 'kill -s USR1 $$') __IN__ test_oE 'command traps are reset in subshell' trap 'echo trapped' USR1 ("$TESTEE" -c 'kill -s USR1 $PPID'; :) kill -l $? __IN__ USR1 __OUT__ test_oE -e 0 'setting new signal trap in subshell' trap 'echo X' USR1 (trap 'echo trapped' USR1; "$TESTEE" -c 'kill -s USR1 $PPID'; :) __IN__ trapped __OUT__ test_oE -e 0 'setting new EXIT in subshell in EXIT' trap '(trap "echo exit" EXIT)' EXIT __IN__ exit __OUT__ test_oE -e 0 'printing traps' -e trap 'echo "a"'"'b'"'\c' USR1 trap >printed_trap trap - USR1 . ./printed_trap kill -s USR1 $$ __IN__ abc __OUT__ test_oE -e 0 'traps are printed even in command substitution' -e trap 'echo "a"'"'b'"'\c' USR1 printed_trap="$(trap)" trap - USR1 eval "$printed_trap" kill -s USR1 $$ __IN__ abc __OUT__ echo 'echo "$@"' > ./- chmod a+x ./- test_oE 'setting command trap that starts with hyphen' PATH=.:$PATH trap -- '- trapped' USR1 kill -s USR1 $$ __IN__ trapped __OUT__ test_o -d 'invalid signal does not kill non-interactive shell' trap '' '' || echo reached __IN__ reached __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/trap-y.tst000066400000000000000000000102771354143602500155400ustar00rootroot00000000000000# trap-y.tst: yash-specific test of the trap built-in test_o 'trap for EXIT is executed just once' "$TESTEE" -c 'trap "echo EXIT 1" EXIT; ./_no_such_command_ ' "$TESTEE" -c 'trap "echo EXIT 2" EXIT; (./_no_such_command_)' "$TESTEE" -ce 'trap "echo EXIT 3" EXIT; ./_no_such_command_ ' "$TESTEE" -ce 'trap "echo EXIT 4" EXIT; (./_no_such_command_)' "$TESTEE" -c 'trap "echo EXIT 5" EXIT; ./_no_such_command_ ; :' "$TESTEE" -c 'trap "echo EXIT 6" EXIT; (./_no_such_command_); :' "$TESTEE" -ce 'trap "echo EXIT 7" EXIT; ./_no_such_command_ ; :' "$TESTEE" -ce 'trap "echo EXIT 8" EXIT; (./_no_such_command_); :' "$TESTEE" -c 'trap "echo EXIT 9" EXIT; ./_no_such_command_ ; (:)' "$TESTEE" -c 'trap "echo EXIT 10" EXIT; (./_no_such_command_); (:)' "$TESTEE" -ce 'trap "echo EXIT 11" EXIT; ./_no_such_command_ ; (:)' "$TESTEE" -ce 'trap "echo EXIT 12" EXIT; (./_no_such_command_); (:)' __IN__ EXIT 1 EXIT 2 EXIT 3 EXIT 4 EXIT 5 EXIT 6 EXIT 7 EXIT 8 EXIT 9 EXIT 10 EXIT 11 EXIT 12 __OUT__ { # In subshell traps other than ignore are cleared. # Output of the trap built-in reflects it after first trap modification. test_oE 'setting new trap in subshell' trap '' USR1 (trap 'echo INT' INT; sh -c 'kill -s USR1 $PPID'; :) __IN__ __OUT__ test_oE 'printing after setting in subshell' trap '' USR1 trap 'echo USR2' USR2 (trap 'echo INT' INT; trap) __IN__ trap -- 'echo INT' INT trap -- '' USR1 __OUT__ test_oE 'printing after non-trap command in subshell' trap '' USR1 trap 'echo USR2' USR2 (echo foo; trap) __IN__ foo trap -- '' USR1 trap -- 'echo USR2' USR2 __OUT__ test_oE 'ignored signal is still ignored in subshell' trap '' USR1 (sh -c 'kill -s USR1 $PPID'; echo reached) __IN__ reached __OUT__ test_oE 'ignored signal is still ignored after setting for another in subshell' trap '' USR1 (trap 'echo USR2' USR2; sh -c 'kill -s USR1 $PPID'; echo reached) __IN__ reached __OUT__ test_oE 'trapped signal is reset in subshell' trap 'echo USR1' USR1 (sh -c 'kill -s USR1 $PPID' && echo not reached) kill -l $? __IN__ USR1 __OUT__ test_oE 'trapped signal is reset after setting for another in subshell' trap 'echo USR1' USR1 (trap 'echo USR2' USR2; sh -c 'kill -s USR1 $PPID' && echo not reached) kill -l $? __IN__ USR1 __OUT__ } test_oE -e 0 'printing all traps (w/o -p)' trap 'echo "a"'"'b'"'\c' USR1 trap 'echo 1 & echo 2 ;' USR2 trap __IN__ trap -- 'echo "a"'\'b\''\c' USR1 trap -- 'echo 1 & echo 2 ;' USR2 __OUT__ test_oE -e 0 'printing all traps (with -p)' trap 'echo "a"'"'b'"'\c' USR1 trap 'echo 1 & echo 2 ;' USR2 trap -p trap --print __IN__ trap -- 'echo "a"'\'b\''\c' USR1 trap -- 'echo 1 & echo 2 ;' USR2 trap -- 'echo "a"'\'b\''\c' USR1 trap -- 'echo 1 & echo 2 ;' USR2 __OUT__ test_oE -e 0 'printing specific traps (with -p)' trap 'echo X' USR1 USR2 HUP trap 'echo Y' INT QUIT trap -p QUIT USR1 __IN__ trap -- 'echo Y' QUIT trap -- 'echo X' USR1 __OUT__ test_oE -e 0 'specifying signal with SIG-prefix' trap 'echo trapped' SIGUSR1 && kill -s USR1 $$ __IN__ trapped __OUT__ test_oE -e 0 'signal name is case-insensitive' trap 'echo trapped' uSr1 && kill -s USR1 $$ __IN__ trapped __OUT__ test_oE 'return interrupts trap (but not function outside trap)' trap 'return; echo not reached' USR1 func() { kill -s USR1 $$ echo reached } func __IN__ reached __OUT__ test_Oe -e 1 'setting trap for KILL' trap '' KILL __IN__ trap: SIGKILL cannot be trapped __ERR__ test_Oe -e 1 'setting trap for STOP' trap '' STOP __IN__ trap: SIGSTOP cannot be trapped __ERR__ test_Oe -e 2 'invalid option' trap --no-such-option __IN__ trap: `--no-such-option' is not a valid option __ERR__ #' #` test_Oe -e 2 'missing operand' trap - __IN__ trap: this command requires 2 operands __ERR__ test_Oe -e 1 'invalid signal name' trap - NOSUCHSIGNAL __IN__ trap: no such signal `NOSUCHSIGNAL' __ERR__ #' #` test_Oe -e 1 'invalid signal number' trap -- - -1 __IN__ trap: no such signal `-1' __ERR__ #' #` test_O -d -e 1 'printing to closed stream: printing all traps (w/o -p)' trap '' USR1 trap >&- __IN__ test_O -d -e 1 'printing to closed stream: printing all traps (with -p)' trap '' USR1 trap -p >&- __IN__ test_O -d -e 1 'printing to closed stream: printing specific traps (with -p)' trap '' USR1 trap -p USR1 >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/typeset-y.tst000066400000000000000000000207001354143602500162570ustar00rootroot00000000000000# typeset-y.tst: yash-specific test of the typeset built-in test_oE -e 0 'typeset is a semi-special built-in' command -V typeset __IN__ typeset: a semi-special built-in __OUT__ test_oE -e 0 'defining variable in global namespace' -e typeset a=1 echo $a __IN__ 1 __OUT__ test_oE -e 0 'defining local variable' -e f() { typeset a=1 b=2 echo $a $b a=3 b=4 echo $a $b } f echo ${a-unset} ${b-unset} __IN__ 1 2 3 4 unset 4 __OUT__ test_oE -e 0 'overwriting temporary variable' -e a=1 typeset a=2 echo $a __IN__ 2 __OUT__ test_oE -e 0 'redeclaring temporary variable' -e a=1 a=2 typeset a echo $a __IN__ 1 __OUT__ test_oE -e 0 'declaring local variable with temporary variable' -e a=1 typeset b echo a=${a-unset} b=${b-unset} __IN__ a=unset b=unset __OUT__ test_oE -e 0 'printing all variables (no option)' -e typeset >/dev/null typeset | grep -q '^typeset -x PATH=' yash_typeset_test=foo typeset | grep -Fx "typeset yash_typeset_test=foo" readonly yash_readonly_test=bar export yash_export_test=baz typeset | grep -Fx "typeset -r yash_readonly_test=bar" typeset | grep -Fx "typeset -x yash_export_test=baz" __IN__ typeset yash_typeset_test=foo typeset -r yash_readonly_test=bar typeset -x yash_export_test=baz __OUT__ test_oE -e 0 'only local variables are printed by default (no option)' -e f() { a=1; typeset; } g() { typeset a=1; typeset; } f echo --- g __IN__ --- typeset a=1 __OUT__ test_oE 'printing all variables (-g)' f() { yash_typeset_test_a=1 typeset yash_typeset_test_b=2 typeset -g } yash_typeset_test_g=3 f | grep '^typeset.* yash_typeset_test_.=' __IN__ typeset yash_typeset_test_a=1 typeset yash_typeset_test_b=2 typeset yash_typeset_test_g=3 __OUT__ test_oE -e 0 'defining and printing local array (no option)' -e f() { typeset a a=(This is my array.) printf '%s\n' "$a" typeset } a=global f echo $a __IN__ This is my array. a=(This is my array.) typeset a global __OUT__ test_oE 'defining read-only variables (-r)' -e a=1 typeset -r a b=2 (typeset a=X 2>/dev/null || echo $a) (typeset b=Y 2>/dev/null || echo $b) __IN__ 1 2 __OUT__ test_oE 'defining exported variables (-x)' -e a=1 typeset -x a b=2 echo $a $b sh -c 'echo $a $b' __IN__ 1 2 1 2 __OUT__ ( export a=1 b=2 test_oE 'canceling exportation of variables (-X)' -e typeset -X a b=3 echo $a $b sh -c 'echo ${a-unset} ${b-unset}' __IN__ 1 3 unset unset __OUT__ ) test_oE -e 0 'printing all variables (-p)' -e typeset -p >/dev/null typeset -p | grep -q '^typeset -x PATH=' yash_typeset_test=foo typeset -p | grep -Fx "typeset yash_typeset_test=foo" readonly yash_readonly_test=bar export yash_export_test=baz typeset -p | grep -Fx "typeset -r yash_readonly_test=bar" typeset -p | grep -Fx "typeset -x yash_export_test=baz" __IN__ typeset yash_typeset_test=foo typeset -r yash_readonly_test=bar typeset -x yash_export_test=baz __OUT__ test_oE -e 0 'only local variables are printed by default (-p)' -e f() { a=1; typeset -p; } g() { typeset a=1; typeset -p; } f echo --- g __IN__ --- typeset a=1 __OUT__ test_oE -e 0 'printing specific variables (-p)' -e a=1 b=2 c=3 typeset -p a b __IN__ typeset a=1 typeset b=2 __OUT__ test_oE -e 0 'printing array variable (-p)' -e a=() b=(1 '2 2' 3) typeset -x b typeset -p a b __IN__ a=() typeset a b=(1 '2 2' 3) typeset -x b __OUT__ test_oE -e 0 'assigning variable with -p' -e a=1 typeset -p a b=2 echo $a $b __IN__ typeset a=1 1 2 __OUT__ test_oE 'printing all variables (-gp)' f() { yash_typeset_test_a=1 typeset yash_typeset_test_b=2 typeset -gp } yash_typeset_test_g=3 f | grep '^typeset.* yash_typeset_test_.=' __IN__ typeset yash_typeset_test_a=1 typeset yash_typeset_test_b=2 typeset yash_typeset_test_g=3 __OUT__ test_oE -e 0 'printing read-only variables (-rp)' -e typeset -r a=1 b=2 typeset -rp a b __IN__ typeset -r a=1 __OUT__ test_oE -e n 'defining read-only variables (-rp)' -e typeset -rp a=1 echo $a a=X 2>/dev/null && test "$a" = 1 __IN__ 1 __OUT__ test_oE -e 0 'printing exported variables (-xp)' -e typeset -x a=1 b=2 typeset -xp a b __IN__ typeset -x a=1 __OUT__ test_oE -e 0 'defining exported variables (-xp)' -e typeset -xp a=1 echo $a sh -c 'echo $a' __IN__ 1 1 __OUT__ test_oE -e 0 'printing variables: -X is ignored with -p (-Xp)' -e a=1 typeset -x b=2 typeset -Xp a b __IN__ typeset a=1 typeset -x b=2 __OUT__ test_oE -e 0 'printing global exported variables (-gxp)' -e g=1 typeset -x h=2 func() { typeset l=3 typeset -x m=4 typeset -gxp g h l m } func __IN__ typeset -x h=2 typeset -x m=4 __OUT__ test_oE 'defining global exported variables (-gxp)' -e func() { typeset -gxp a=1; } func sh -c 'echo $a' __IN__ 1 __OUT__ test_oE -e 0 'printing read-only exported variables (-rxp)' -e typeset n=neither typeset -r r=readonly typeset -x x=exported typeset -rx b=both typeset -rxp n r x b __IN__ typeset -xr b=both __OUT__ test_oE 'defining read-only exported variables (-rxp)' -e typeset -rxp a=1 sh -c 'echo $a' (typeset a=X 2>/dev/null || echo $a) __IN__ 1 1 __OUT__ test_oE -e 0 'printing read-only variables: -X is ignored with -p (-rXp)' -e typeset -r a=1 b=2 typeset -rXp a b __IN__ typeset -r a=1 __OUT__ test_oE -e n 'defining read-only un-exported variables (-rXp)' -e typeset -x a=0 typeset -rXp a=1 echo $a sh -c 'echo ${a-unset}' a=X 2>/dev/null && test "$a" = 1 __IN__ 1 unset __OUT__ test_x -e 0 'printing all functions (-f): exit status' -e f() { } g() for i in 1; do echo $i; done typeset -f __IN__ test_oE 'printing all functions (-f): output' -e f() { } g() for i in 1; do echo $i; done typeset -f | sed 's;^[[:space:]]*;;' __IN__ f() { } g() for i in 1 do echo ${i} done __OUT__ test_OE -e 0 'testing existence of functions (-f)' -e f() { } g() for i in 1; do echo $i; done typeset -f f g __IN__ test_oe -e n 'making function readonly (-fr)' -e f() { echo f; } g() { echo g; } typeset -fr f f g eval 'f() { }' __IN__ f g __OUT__ eval: function `f' cannot be redefined because it is read-only __ERR__ #' #` test_x -e 0 'printing all functions (-fp): exit status' -e f() { } g() for i in 1; do echo $i; done typeset -fp __IN__ test_oE 'printing all functions (-fp): output' -e f() { } g() for i in 1; do echo $i; done typeset -fp | sed 's;^[[:space:]]*;;' __IN__ f() { } g() for i in 1 do echo ${i} done __OUT__ test_oE -e 0 'printing specific functions (-fp)' -e f() { } g() ( ) h() for i in 1; do echo $i; done typeset -fp f g __IN__ f() { } g() () __OUT__ test_oE -e 0 'printing function with non-portable name (-fp)' -e function f=/\'g() { } typeset -fp "f=/'g" __IN__ function 'f=/'\'g() { } __OUT__ test_oE 'printing function with command substitution with subshell (-fp)' -e eval "$( print_foo() { echo "$((echo foo) )" } typeset -fp print_foo )" print_foo __IN__ foo __OUT__ test_oE -e 0 'printing read-only function (-frp)' -e f() { } g() ( ) typeset -fr f typeset -frp f g __IN__ f() { } typeset -fr f __OUT__ test_Oe -e 2 'invalid option -z' typeset -z __IN__ typeset: `-z' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' typeset --no-such=option __IN__ typeset: `--no-such=option' is not a valid option __ERR__ #' #` test_Oe -e 2 'specifying -x and -X at once' typeset -xX __IN__ typeset: the -x option cannot be used with the -X option __ERR__ test_Oe -e 2 'specifying -f and -g at once' typeset -fg __IN__ typeset: the -f option cannot be used with the -g option __ERR__ test_Oe -e 2 'specifying -f and -x at once' typeset -fx __IN__ typeset: the -f option cannot be used with the -x option __ERR__ test_Oe -e 2 'specifying -f and -X at once' typeset -fX __IN__ typeset: the -f option cannot be used with the -X option __ERR__ test_O -d -e 1 'printing to closed output stream (all variables w/o -p)' typeset >&- __IN__ test_O -d -e 1 'printing to closed output stream (all variables with -p)' typeset -p >&- __IN__ test_O -d -e 1 'printing to closed output stream (specific variable)' typeset -p PWD >&- __IN__ ( setup 'func() { :; }' test_O -d -e 1 'printing to closed output stream (all functions w/o -p)' typeset -f >&- __IN__ test_O -d -e 1 'printing to closed output stream (all functions with -p)' typeset -fp >&- __IN__ test_O -d -e 1 'printing to closed output stream (specific function)' typeset -fp func >&- __IN__ ) test_Oe -e 1 'assigning to read-only variable' typeset -r a typeset a=1 __IN__ typeset: $a is read-only __ERR__ test_Oe -e 1 'printing non-existing variable' typeset -p a __IN__ typeset: no such variable $a __ERR__ test_Oe -e 1 'printing non-existing function' typeset -fp a __IN__ typeset: no such function `a' __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/ulimit-y.tst000066400000000000000000000020331354143602500160640ustar00rootroot00000000000000# ulimit-y.tst: yash-specific test of the ulimit built-in if ! testee -c 'command -bv ulimit' >/dev/null; then skip="true" fi test_Oe -e 2 'too many operands (w/o -a)' ulimit 0 0 __IN__ ulimit: too many operands are specified __ERR__ test_Oe -e 2 'too many operands (with -a)' ulimit -a 0 __IN__ ulimit: no operand is expected __ERR__ test_Oe -e 2 'invalid option --xxx' ulimit --no-such=option __IN__ ulimit: `--no-such=option' is not a valid option __ERR__ #' #` test_Oe -e 2 'specifying -a and -f at once' ulimit -a -f __IN__ ulimit: the -a option cannot be used with the -f option __ERR__ test_Oe -e 2 'invalid operand (non-numeric)' ulimit X __IN__ ulimit: `X' is not a valid integer __ERR__ #' #` test_Oe -e 2 'invalid operand (non-integral)' ulimit 1.0 __IN__ ulimit: `1.0' is not a valid integer __ERR__ #' #` test_Oe -e 2 'invalid operand (negative)' ulimit -- -1 __IN__ ulimit: `-1' is not a valid integer __ERR__ #' #` test_O -d -e 1 'printing to closed output stream' ulimit >&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/umask-p.tst000066400000000000000000000110441354143602500156720ustar00rootroot00000000000000# umask-p.tst: test of the umask built-in for any POSIX-compliant shell posix="true" # $1 = $LINENO, $2 = umask test_restore_non_symbolic() { testcase "$1" -e 0 \ "restoring umask using previous output, non-symbolic, $2" \ 3<<\__IN__ 4&- __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/unset-p.tst000066400000000000000000000037601354143602500157160ustar00rootroot00000000000000# unset-p.tst: test of the unset built-in for any POSIX-compliant shell posix="true" echo echo external a > a echo echo external b > b echo echo external c > c echo echo external d > d echo echo external x > x chmod a+x a b c d x setup 'PATH=.:$PATH' test_oE -e 0 'deleting existing variable (default)' -e a=1 b=2 unset a echo ${a-unset} ${b-unset} __IN__ unset 2 __OUT__ test_oE -e 0 'deleting non-existing variable (default)' -e a=1 b=2 unset x echo ${a-unset} ${b-unset} ${x-unset} __IN__ 1 2 unset __OUT__ test_oE -e 0 'deleting many variables (default)' -e a=1 b=2 c=3 d=4 unset a b x c echo ${a-unset} ${b-unset} ${c-unset} ${d-unset} ${x-unset} __IN__ unset unset unset 4 unset __OUT__ test_oE -e 0 'only variable is deleted by default' -e a() { echo "$@"; } a=1 unset a a ${a-unset} __IN__ unset __OUT__ test_oE -e 0 'deleting many variables (-v)' -e a=1 b=2 c=3 d=4 unset -v a b x c echo ${a-unset} ${b-unset} ${c-unset} ${d-unset} ${x-unset} __IN__ unset unset unset 4 unset __OUT__ test_oE -e 0 'only variable is deleted (-v)' -e a() { echo "$@"; } a=1 unset -v a a ${a-unset} __IN__ unset __OUT__ test_oE -e 0 'deleting existing function (-f)' -e a() { echo a; } b() { echo b; } unset -f b a b __IN__ a external b __OUT__ test_oE -e 0 'deleting non-existing function (-f)' -e a() { echo a; } unset -f b a b __IN__ a external b __OUT__ test_oE -e 0 'deleting many functions (-f)' -e a() { echo a; } b() { echo b; } c() { echo c; } d() { echo d; } unset -f a b x c a b c d x __IN__ external a external b external c d external x __OUT__ test_oE -e 0 'only function is deleted (-f)' -e a=1 a() { echo a; } unset -f a echo ${a-unset} __IN__ 1 __OUT__ test_O -d -e n 'read-only variable cannot be deleted (default)' readonly a= unset a echo not reached # special built-in error kills non-interactive shell __IN__ test_O -d -e n 'read-only variable cannot be deleted (-v)' readonly a= unset -v a echo not reached # special built-in error kills non-interactive shell __IN__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/unset-y.tst000066400000000000000000000064121354143602500157240ustar00rootroot00000000000000# unset-y.tst: yash-specific test of the unset built-in echo echo external a > a echo echo external b > b echo echo external c > c echo echo external d > d echo echo external x > x chmod a+x a b c d x setup 'PATH=.:$PATH' test_oE -e 0 'deleting nothing (default)' -e a=1 a() { echo function; } unset echo ${a-unset} a __IN__ 1 function __OUT__ test_oE -e 0 'deleting nothing (-v)' -e a=1 a() { echo function; } unset -v echo ${a-unset} a __IN__ 1 function __OUT__ test_oE -e 0 'deleting nothing (-f)' -e a=1 a() { echo function; } unset -f echo ${a-unset} a __IN__ 1 function __OUT__ test_oE -e 0 'deleting existing variable (--variables)' -e a=1 b=2 unset --variables a echo ${a-unset} ${b-unset} __IN__ unset 2 __OUT__ test_oE -e 0 'deleting non-existing variable (--variables)' -e a=1 b=2 unset --variables x echo ${a-unset} ${b-unset} ${x-unset} __IN__ 1 2 unset __OUT__ test_oE -e 0 'deleting many variables (--variables)' -e a=1 b=2 c=3 d=4 unset --variables a b x c echo ${a-unset} ${b-unset} ${c-unset} ${d-unset} ${x-unset} __IN__ unset unset unset 4 unset __OUT__ test_oE -e 0 'only variable is deleted (--variables)' -e a() { echo "$@"; } a=1 unset --variables a a ${a-unset} __IN__ unset __OUT__ test_oE -e 0 'deleting array variable (--variables)' -e a=() unset --variables a echo ${a-unset} __IN__ unset __OUT__ test_oE -e 0 'deleting local variable (--variables)' -e f() { typeset a=local unset a echo $a } a=global f echo $a __IN__ global global __OUT__ test_oE -e 0 'deleting existing function (--functions)' -e a() { echo a; } b() { echo b; } unset --functions b a b __IN__ a external b __OUT__ test_oE -e 0 'deleting non-existing function (--functions)' -e a() { echo a; } unset --functions b a b __IN__ a external b __OUT__ test_oE -e 0 'deleting many functions (--functions)' -e a() { echo a; } b() { echo b; } c() { echo c; } d() { echo d; } unset --functions a b x c a b c d x __IN__ external a external b external c d external x __OUT__ test_oE -e 0 'only function is deleted (--functions)' -e a=1 a() { echo a; } unset --functions a echo ${a-unset} __IN__ 1 __OUT__ test_oE -e 0 'function is not deleted by default' -e a() { echo function; } unset a a __IN__ function __OUT__ test_oE -e 0 'last-specified option takes effect (-v)' -e a=1 a() { echo function; } unset -fv a echo ${a-unset} a __IN__ unset function __OUT__ test_oE -e 0 'last-specified option takes effect (-f)' -e a=1 a() { echo function; } unset -vf a echo ${a-unset} a __IN__ 1 external a __OUT__ test_oE -e 0 'invalid variable names are ignored' -e set 1 2 3 unset = =foo echo "$@" __IN__ 1 2 3 __OUT__ test_oE 'deleting function with non-portable name' -e function f=/\'g() { } unset -f f=/\'g command -fv f=/\'g || echo function unset __IN__ function unset __OUT__ test_Oe -e 1 'deleting read-only variable' readonly a=1 unset a __IN__ unset: $a is read-only __ERR__ test_oe 'deleting read-only function' func() { echo func; } readonly -f func unset -f func echo $? func __IN__ 1 func __OUT__ unset: function `func' is read-only __ERR__ #' #` test_Oe -e 2 'invalid option -z' unset -z __IN__ unset: `-z' is not a valid option __ERR__ #' #` test_Oe -e 2 'invalid option --xxx' unset --no-such=option __IN__ unset: `--no-such=option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/until-p.tst000066400000000000000000000036561354143602500157170ustar00rootroot00000000000000# until-p.tst: test of until loop for any POSIX-compliant shell posix="true" test_oE 'execution path of 0-round loop' i=0 until [ $((i=i+1)) -gt 0 ];do echo $i;done echo done $i __IN__ done 1 __OUT__ test_oE 'execution path of 1-round loop' i=0 until [ $((i=i+1)) -gt 1 ];do echo $i;done echo done $i __IN__ 1 done 2 __OUT__ test_oE 'execution path of 2-round loop' i=0 until [ $((i=i+1)) -gt 2 ];do echo $i;done echo done $i __IN__ 1 2 done 3 __OUT__ ( setup <<\__END__ \unalias \x x() { return $1; } __END__ test_x -e 0 'exit status of 0-round loop' until true;do :;done __IN__ test_x -e 1 'exit status of 1-round loop' i=0 until [ $((i=i+1)) -gt 1 ];do x $i;done __IN__ test_x -e 2 'exit status of 2-round loop' i=0 until [ $((i=i+1)) -gt 2 ];do x $i;done __IN__ ) test_oE 'linebreak after until' i=0 until [ $((i=i+1)) -gt 2 ];do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak before do' i=0 until [ $((i=i+1)) -gt 2 ] do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak after do' i=0 until [ $((i=i+1)) -gt 2 ];do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak before done' i=0 until [ $((i=i+1)) -gt 2 ];do echo $i done __IN__ 1 2 __OUT__ test_oE 'command ending with asynchronous command (condition)' until echo foo&do echo not reached;break;done;wait __IN__ foo __OUT__ test_oE 'command ending with asynchronous command (body)' i=0 until [ $((i=i+1)) -gt 1 ];do echo $i&done wait __IN__ 1 __OUT__ test_oE 'more than one inner command' i=0 until i=$((i+1)); [ $i -gt 2 ];do echo $i;echo -;done __IN__ 1 - 2 - __OUT__ test_oE 'nest between until and do' i=0 until { [ $((i=i+1)) -gt 1 ]; } do echo $i;done __IN__ 1 __OUT__ test_oE 'nest between do and done' i=0 until [ $((i=i+1)) -gt 1 ]; do { echo $i;} done __IN__ 1 __OUT__ test_oE 'redirection on until loop' i=0 until echo -;[ $((i=i+1)) -gt 1 ];do echo $i;done >redir_out cat redir_out __IN__ - 1 - __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/until-y.tst000066400000000000000000000046261354143602500157260ustar00rootroot00000000000000# until-y.tst: yash-specific test of until loop test_OE 'effect of empty condition (true)' true until do echo not reached; break; done __IN__ test_OE 'effect of empty condition (false)' false until do echo not reached; break; done __IN__ test_oE 'effect of empty body' i=0 until [ $((i=i+1)) -gt 2 ];do done echo $i __IN__ 3 __OUT__ test_OE -e 0 'exit status with empty condition' false until do echo not reached; break; done __IN__ test_OE -e 0 'exit status with empty body (0-round loop)' i=0 until [ $((i=i+1)) -gt 0 ];do done __IN__ test_OE -e 0 'exit status with empty body (1-round loop)' i=0 until [ $((i=i+1)) -gt 1 ];do done __IN__ test_OE -e 0 'exit status with empty body (2-round loop)' i=0 until [ $((i=i+1)) -gt 2 ];do done __IN__ ( posix="true" test_Oe -e 2 'POSIX: empty condition (single line)' until do echo not reached; done __IN__ syntax error: commands are missing after `until' __ERR__ #' #` test_Oe -e 2 'POSIX: empty condition (multi-line)' until do echo not reached; done __IN__ syntax error: commands are missing after `until' __ERR__ #' #` test_Oe -e 2 'POSIX: empty body (single line)' until echo not reached; do done __IN__ syntax error: commands are missing between `do' and `done' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (multi-line)' until echo not reached; do done __IN__ syntax error: commands are missing between `do' and `done' __ERR__ #' #` #' #` ) test_Oe -e 2 'missing do' until echo not reached; done __IN__ syntax error: encountered `done' without a matching `do' syntax error: (maybe you missed `do'?) __ERR__ #' #` #' #` #' #` test_Oe -e 2 'missing do and done' until echo not reached __IN__ syntax error: `do' is missing syntax error: `done' is missing __ERR__ #' #` #' #` test_Oe -e 2 'missing done' until echo not reached 1; do echo not reached 2 __IN__ syntax error: `done' is missing __ERR__ #' #` test_Oe -e 2 'missing do and done (in grouping)' { until } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `do'?) syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `done'?) __ERR__ #' #` #' #` #' #` #' #` #' #` #' #` test_Oe -e 2 'missing done (in grouping)' { until do } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `done'?) __ERR__ #' #` #' #` #' #` # `do' and `done' without `until` are tested in for-y.tst. # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/valgrind.supp000066400000000000000000000007021354143602500162770ustar00rootroot00000000000000{ Memcheck:Cond:re_compile_fastmap_iter/re_compile_fastmap/regcomp Memcheck:Cond fun:re_compile_fastmap_iter* fun:re_compile_fastmap fun:regcomp } { Memcheck:Cond:wcsnlen Memcheck:Cond fun:__wcsnlen_sse4_1 } { Memcheck:Addr16:wcsnlen Memcheck:Addr16 fun:__wcsnlen_sse4_1 } { Memcheck:Leak:getpwnam_r@@GLIBC_2.2.5 Memcheck:Leak match-leak-kinds: definite fun:malloc ... fun:getpwnam_r@@GLIBC_2.2.5 } yash-2.49/tests/wait-p.tst000066400000000000000000000040561354143602500155230ustar00rootroot00000000000000# wait-p.tst: test of the wait built-in for any POSIX-compliant shell ../checkfg || skip="true" # %REQUIRETTY% posix="true" test_oE 'waiting for all jobs (+m)' echo a > a& echo b > b& echo c > c& exit 1& wait cat a b c __IN__ a b c __OUT__ test_OE -e 11 'waiting for specific single job (+m)' exit 11& wait $! __IN__ test_OE -e 1 'waiting for specific many jobs (+m)' exit 1& p1=$! exit 2& p2=$! exit 3& p3=$! wait $p3 $p2 $p1 __IN__ test_OE -e 127 'waiting for unknown job (+m)' exit 1& wait $! $(($!+1)) __IN__ test_OE -e 127 'jobs are not inherited to subshells (+m, -s)' exit 1& p=$! (wait $p) __IN__ test_OE -e 127 'jobs are not inherited to subshells (+m, -c)' \ -c 'exit 1& p=$!; (wait $p)' __IN__ test_OE -e 1 'jobs are not propagated from subshells (+m)' exit 1& (exit 2&) wait $! __IN__ test_oE 'waiting for all jobs (-m)' -m echo a > a& echo b > b& echo c > c& exit 1& wait cat a b c __IN__ a b c __OUT__ test_OE -e 11 'waiting for specific single job (-m)' -m exit 11& wait $! __IN__ test_OE -e 1 'waiting for specific many jobs (-m)' -m exit 1& p1=$! exit 2& p2=$! exit 3& p3=$! wait $p3 $p2 $p1 __IN__ test_OE -e 127 'waiting for unknown job (-m)' -m exit 1& wait $! $(($!+1)) __IN__ test_oE -e 11 'specifying job ID' -m cat /dev/null& echo 1& exit 11& wait %echo %exit __IN__ 1 __OUT__ test_OE -e 127 'jobs are not inherited to subshells (-m, -s)' -m exit 1& p=$! (wait $p) __IN__ test_OE -e 127 'jobs are not inherited to subshells (+m, -c)' \ -cm 'exit 1& p=$!; (wait $p)' __IN__ test_OE -e 1 'jobs are not propagated from subshells (-m)' -m exit 1& (exit 2&) wait $! __IN__ test_oE 'trap interrupts wait' -m interrupted=false trap 'interrupted=true' USR1 ( set +m trap 'echo received USR2; exit' USR2 while kill -s USR1 $$; do sleep 1; done # loop until signaled )& wait $! status=$? echo interrupted=$interrupted $((status > 128)) kill -l $status # Now, the background job should be still running. kill -s USR2 % wait $! echo waited $? __IN__ interrupted=true 1 USR1 received USR2 waited 0 __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/wait-y.tst000066400000000000000000000034671354143602500155410ustar00rootroot00000000000000# wait-y.tst: yash-specific test of the wait built-in ../checkfg || skip="true" # %REQUIRETTY% mkfifo sync test_x -e 1 'invalid job specification overrides exit status of valid job (1)' exit 42 & wait X $! __IN__ test_x -e 1 'invalid job specification overrides exit status of valid job (2)' exit 42 & wait $! X __IN__ test_o -e 0 'awaited job is printed (with operand, -im, non-POSIX)' -im # The "jobs" command ensures the "wait" command does not print "Running". >sync& jobs; cat sync; echo -; wait __IN__ [1] + Running 1>sync - [1] + Done 1>sync __OUT__ test_O -e 0 'awaited job is not printed (with operand, -i +m)' -i +m >sync& cat sync; cat /dev/null; wait % __IN__ test_O -e 0 'awaited job is not printed (with operand, +i -m)' -m >sync& cat sync; cat /dev/null; wait % __IN__ test_O -e 0 'awaited job is not printed (with operand, -im, POSIX)' -im --posix >sync& cat sync; cat /dev/null; wait % __IN__ test_o -e 0 'awaited job is printed (w/o operand, -im, non-POSIX)' -im # The "jobs" command ensures the "wait" command does not print "Running". >sync& jobs; cat sync; echo -; wait __IN__ [1] + Running 1>sync - [1] + Done 1>sync __OUT__ test_O -e 0 'awaited job is not printed (w/o operand, -i +m)' -i +m >sync& cat sync; cat /dev/null; wait __IN__ test_O -e 0 'awaited job is not printed (w/o operand, +i -m)' -m >sync& cat sync; cat /dev/null; wait __IN__ test_O -e 0 'awaited job is not printed (w/o operand, -im, POSIX)' -im --posix >sync& cat sync; cat /dev/null; wait __IN__ test_x -e 127 'job is forgotten after awaited' -im exec >sync && exit 17 & pid=$! cat sync : : : wait wait $pid __IN__ test_Oe -e 2 'invalid option --xxx' wait --no-such=option __IN__ wait: `--no-such=option' is not a valid option __ERR__ #' #` # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/while-p.tst000066400000000000000000000036361354143602500156720ustar00rootroot00000000000000# while-p.tst: test of while loop for any POSIX-compliant shell posix="true" test_oE 'execution path of 0-round loop' i=0 while [ $((i=i+1)) -le 0 ];do echo $i;done echo done $i __IN__ done 1 __OUT__ test_oE 'execution path of 1-round loop' i=0 while [ $((i=i+1)) -le 1 ];do echo $i;done echo done $i __IN__ 1 done 2 __OUT__ test_oE 'execution path of 2-round loop' i=0 while [ $((i=i+1)) -le 2 ];do echo $i;done echo done $i __IN__ 1 2 done 3 __OUT__ ( setup <<\__END__ \unalias \x x() { return $1; } __END__ test_x -e 0 'exit status of 0-round loop' while false;do :;done __IN__ test_x -e 1 'exit status of 1-round loop' i=0 while [ $((i=i+1)) -le 1 ];do x $i;done __IN__ test_x -e 2 'exit status of 2-round loop' i=0 while [ $((i=i+1)) -le 2 ];do x $i;done __IN__ ) test_oE 'linebreak after while' i=0 while [ $((i=i+1)) -le 2 ];do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak before do' i=0 while [ $((i=i+1)) -le 2 ] do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak after do' i=0 while [ $((i=i+1)) -le 2 ];do echo $i;done __IN__ 1 2 __OUT__ test_oE 'linebreak before done' i=0 while [ $((i=i+1)) -le 2 ];do echo $i done __IN__ 1 2 __OUT__ test_oE 'command ending with asynchronous command (condition)' while echo foo&do break;done;wait __IN__ foo __OUT__ test_oE 'command ending with asynchronous command (body)' i=0 while [ $((i=i+1)) -le 1 ];do echo $i&done wait __IN__ 1 __OUT__ test_oE 'more than one inner command' i=0 while i=$((i+1)); [ $i -le 2 ];do echo $i;echo -;done __IN__ 1 - 2 - __OUT__ test_oE 'nest between while and do' i=0 while { [ $((i=i+1)) -le 1 ]; } do echo $i;done __IN__ 1 __OUT__ test_oE 'nest between do and done' i=0 while [ $((i=i+1)) -le 1 ]; do { echo $i;} done __IN__ 1 __OUT__ test_oE 'redirection on while loop' i=0 while echo -;[ $((i=i+1)) -le 1 ];do echo $i;done >redir_out cat redir_out __IN__ - 1 - __OUT__ # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/tests/while-y.tst000066400000000000000000000045761354143602500157070ustar00rootroot00000000000000# while-y.tst: yash-specific test of while loop test_oE 'effect of empty condition (true)' true while do echo -; break; done __IN__ - __OUT__ test_oE 'effect of empty condition (false)' false while do echo -; break; done __IN__ - __OUT__ test_oE 'effect of empty body' i=0 while [ $((i=i+1)) -le 2 ];do done echo $i __IN__ 3 __OUT__ test_OE -e 0 'exit status with empty condition' while do break; done __IN__ test_OE -e 0 'exit status with empty body (0-round loop)' i=0 while [ $((i=i+1)) -le 0 ];do done __IN__ test_OE -e 0 'exit status with empty body (1-round loop)' i=0 while [ $((i=i+1)) -le 1 ];do done __IN__ test_OE -e 0 'exit status with empty body (2-round loop)' i=0 while [ $((i=i+1)) -le 2 ];do done __IN__ ( posix="true" test_Oe -e 2 'POSIX: empty condition (single line)' while do echo not reached; done __IN__ syntax error: commands are missing after `while' __ERR__ #' #` test_Oe -e 2 'POSIX: empty condition (multi-line)' while do echo not reached; done __IN__ syntax error: commands are missing after `while' __ERR__ #' #` test_Oe -e 2 'POSIX: empty body (single line)' while echo not reached; do done __IN__ syntax error: commands are missing between `do' and `done' __ERR__ #' #` #' #` test_Oe -e 2 'POSIX: empty body (multi-line)' while echo not reached; do done __IN__ syntax error: commands are missing between `do' and `done' __ERR__ #' #` #' #` ) test_Oe -e 2 'missing do' while echo not reached; done __IN__ syntax error: encountered `done' without a matching `do' syntax error: (maybe you missed `do'?) __ERR__ #' #` #' #` #' #` test_Oe -e 2 'missing do and done' while echo not reached __IN__ syntax error: `do' is missing syntax error: `done' is missing __ERR__ #' #` #' #` test_Oe -e 2 'missing done' while echo not reached 1; do echo not reached 2 __IN__ syntax error: `done' is missing __ERR__ #' #` test_Oe -e 2 'missing do and done (in grouping)' { while } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `do'?) syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `done'?) __ERR__ #' #` #' #` #' #` #' #` #' #` #' #` test_Oe -e 2 'missing done (in grouping)' { while do } __IN__ syntax error: encountered `}' without a matching `{' syntax error: (maybe you missed `done'?) __ERR__ #' #` #' #` #' #` # `do' and `done' without `while` are tested in for-y.tst. # vim: set ft=sh ts=8 sts=4 sw=4 noet: yash-2.49/util.c000066400000000000000000000175621354143602500135530ustar00rootroot00000000000000/* Yash: yet another shell */ /* util.c: miscellaneous utility functions */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "util.h" #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "exec.h" #include "option.h" #include "plist.h" /********** Memory Utilities **********/ /* This function is called on memory allocation failure and * aborts the program after printing an error message. */ void alloc_failed(void) { xerror(ENOMEM, NULL); abort(); } /********** String Utilities **********/ #if !HAVE_STRNLEN /* Returns min(maxlen, strlen(s)). */ size_t xstrnlen(const char *s, size_t maxlen) { size_t result = 0; while (result < maxlen && s[result] != '\0') result++; return result; } #endif #if !HAVE_STRDUP /* Returns a newly malloced copy of the specified string. * Aborts the program on malloc failure. */ char *xstrdup(const char *s) { size_t len = strlen(s); // char *result = xmalloce(len, 1, sizeof (char)); char *result = xmalloc(add(len, 1)); result[len] = '\0'; return memcpy(result, s, len); } #endif #if !HAVE_WCSNLEN /* Returns min(maxlen, wcslen(s)). */ size_t xwcsnlen(const wchar_t *s, size_t maxlen) { size_t result = 0; while (result < maxlen && s[result] != L'\0') result++; return result; } #endif /* Returns a newly malloced copy of the specified string. * The copy is at most `len' characters long. * Returns an exact copy if (wcslen(s) <= len). * Aborts the program on malloc failure. */ wchar_t *xwcsndup(const wchar_t *s, size_t len) { len = xwcsnlen(s, len); wchar_t *result = xmalloce(len, 1, sizeof (wchar_t)); result[len] = L'\0'; return wmemcpy(result, s, len); } /* Converts the specified multibyte string into an integer value. * If successful, stores the integer value in `*resultp', sets `errno' to zero, * and returns true. Otherwise, sets `errno' to a non-zero error value and * returns false (the value of `*resultp' is undefined). * The conversion is considered successful if the string is not empty, the * `strtol' function does not set `errno' to non-zero, and there is no remaining * character after the value. * Spaces at the beginning of the string are ignored. */ bool xstrtoi(const char *s, int base, int *resultp) { long result; char *endp; if (*s == '\0') { errno = EINVAL; return false; } errno = 0; result = strtol(s, &endp, base); if (errno != 0) return false; if (*endp != '\0') { errno = EINVAL; return false; } if (result < INT_MIN || result > INT_MAX) { errno = ERANGE; return false; } *resultp = (int) result; return true; } /* Converts the specified wide string into an integer value. * If successful, stores the integer value in `*resultp', sets `errno' to zero, * and returns true. Otherwise, sets `errno' to a non-zero error value and * returns false (the value of `*resultp' is undefined). * The conversion is considered successful if the string is not empty, the * `wcstol' function does not set `errno' to non-zero, and there is no remaining * character after the value. * Spaces at the beginning of the string are ignored. */ bool xwcstoi(const wchar_t *s, int base, int *resultp) { long result; if (!xwcstol(s, base, &result)) return false; if (result < INT_MIN || result > INT_MAX) { errno = ERANGE; return false; } *resultp = (int) result; return true; } /* The `long' version of `xwcstoi'. */ bool xwcstol(const wchar_t *s, int base, long *resultp) { wchar_t *endp; if (*s == L'\0') { errno = EINVAL; return false; } errno = 0; *resultp = wcstol(s, &endp, base); if (errno != 0) return false; if (*endp != L'\0') { errno = EINVAL; return false; } return true; } /* The `unsigned long' version of `xwcstoi'. */ bool xwcstoul(const wchar_t *s, int base, unsigned long *resultp) { wchar_t *endp; if (*s == L'\0') { errno = EINVAL; return false; } errno = 0; *resultp = wcstoul(s, &endp, base); if (errno != 0) return false; if (*endp != L'\0') { errno = EINVAL; return false; } return true; } /* If string `s' starts with `prefix', returns a pointer to the byte right after * the prefix in `s'. Otherwise, returns NULL. * This function does not change the value of `errno'. */ char *matchstrprefix(const char *s, const char *prefix) { while (*prefix != '\0') { if (*prefix != *s) return NULL; prefix++, s++; } return (char *) s; } /* If string `s' starts with `prefix', returns a pointer to the character right * after the prefix in `s'. Otherwise, returns NULL. * This function does not change the value of `errno'. */ wchar_t *matchwcsprefix(const wchar_t *s, const wchar_t *prefix) { while (*prefix != L'\0') { if (*prefix != *s) return NULL; prefix++, s++; } return (wchar_t *) s; } /* Same as `xwcsdup', except that the argument and the return value are of type * (void *). */ void *copyaswcs(const void *p) { return xwcsdup(p); } /********** Error Utilities **********/ /* The name of the current shell process. This value is the first argument to * the main function of the shell. */ const wchar_t *yash_program_invocation_name; /* The basename of `yash_program_invocation_name'. */ const wchar_t *yash_program_invocation_short_name; /* The name of the currently executed built-in. */ const wchar_t *current_builtin_name = NULL; /* The number of calls to the `xerror' function. */ /* This value is reset each time before a built-in is invoked. */ unsigned yash_error_message_count = 0; /* Prints the specified error message to the standard error. * `format' is passed to `gettext' and the result is printed using `vfprintf'. * If `errno_' is non-zero, prints `strerror(errno_)' after the formatted * string. * `format' should not end with '\n' because this function automatically prints * a newline lastly. If (format == NULL && errno_ == 0), prints "unknown error". */ void xerror(int errno_, const char *restrict format, ...) { yash_error_message_count++; fprintf(stderr, "%ls: ", current_builtin_name != NULL ? current_builtin_name : yash_program_invocation_name); if (format == NULL && errno_ == 0) format = Ngt("unknown error"); if (format != NULL) { va_list ap; va_start(ap, format); vfprintf(stderr, gt(format), ap); va_end(ap); } if (errno_ != 0) { fprintf(stderr, (format == NULL) ? "%s" : ": %s", strerror(errno_)); } fputc('\n', stderr); } /* Prints a formatted string like `printf', but if failed to write to the * standard output, writes an error message to the standard error. * Returns true iff successful. When this function returns, the value of `errno' * may not be the one set by the `printf' function. */ /* The `format' string is not passed to `gettext'. */ bool xprintf(const char *restrict format, ...) { va_list ap; int result; va_start(ap, format); result = vprintf(format, ap); va_end(ap); if (result >= 0) { return true; } else { xerror(errno, Ngt("cannot print to the standard output")); return false; } } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/util.h000066400000000000000000000206011354143602500135440ustar00rootroot00000000000000/* Yash: yet another shell */ /* util.h: miscellaneous utility functions */ /* (C) 2007-2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_UTIL_H #define YASH_UTIL_H #include #define Size_max ((size_t) -1) // = SIZE_MAX /********** Miscellaneous Functions **********/ static inline int xunsetenv(const char *name) __attribute__((nonnull)); /* Removes the environment variable of the specified name. * This function wraps the `unsetenv' function, which has an incompatible * prototype on some old environments. */ int xunsetenv(const char *name) { #if UNSETENV_RETURNS_INT return unsetenv(name); #else unsetenv(name); return 0; #endif } /********** Memory Functions **********/ static inline size_t add(size_t a, size_t b) __attribute__((pure)); static inline size_t mul(size_t a, size_t b) __attribute__((pure)); static inline void *xcalloc(size_t nmemb, size_t size) __attribute__((malloc,warn_unused_result)); static inline void *xmalloc(size_t size) __attribute__((malloc,warn_unused_result)); static inline void *xmallocn(size_t count, size_t elemsize) __attribute__((malloc,warn_unused_result)); static inline void *xmalloce(size_t count1, size_t count2, size_t elemsize) __attribute__((malloc,warn_unused_result)); static inline void *xmallocs(size_t mainsize, size_t count, size_t elemsize) __attribute__((malloc,warn_unused_result)); static inline void *xrealloc(void *ptr, size_t size) __attribute__((malloc,warn_unused_result)); static inline void *xreallocn(void *ptr, size_t count, size_t elemsize) __attribute__((malloc,warn_unused_result)); static inline void *xrealloce(void *ptr, size_t count1, size_t count2, size_t elemsize) __attribute__((malloc,warn_unused_result)); static inline void *xreallocs(void *ptr, size_t mainsize, size_t count, size_t elemsize) __attribute__((malloc,warn_unused_result)); extern void alloc_failed(void) __attribute__((noreturn)); /* Computes `a + b', but aborts the program by ENOMEM if the result overflows. */ size_t add(size_t a, size_t b) { size_t sum = a + b; if (sum < a) alloc_failed(); return sum; } /* Computes `a * b', but aborts the program by ENOMEM if the result overflows. */ size_t mul(size_t a, size_t b) { size_t product = a * b; if (b != 0 && product / b != a) alloc_failed(); return product; } /* Attempts `calloc' and aborts the program on failure. */ void *xcalloc(size_t nmemb, size_t size) { void *result = calloc(nmemb, size); if (result == NULL && nmemb > 0 && size > 0) alloc_failed(); return result; } /* Attempts `malloc' and aborts the program on failure. */ void *xmalloc(size_t size) { void *result = malloc(size); if (result == NULL && size > 0) alloc_failed(); return result; } /* Like `xmalloc(count * elemsize)', but aborts the program if the size is too * large. */ void *xmallocn(size_t count, size_t elemsize) { return xmalloc(mul(count, elemsize)); } /* Like `xmalloc((count1 + count2) * elemsize)', but aborts the program if the * size is too large. */ void *xmalloce(size_t count1, size_t count2, size_t elemsize) { return xmallocn(add(count1, count2), elemsize); } /* Like `xmalloc(mainsize + count * elemsize)', but aborts the program if the * size is too large. */ void *xmallocs(size_t mainsize, size_t count, size_t elemsize) { return xmalloc(add(mainsize, mul(count, elemsize))); } /* Attempts `realloc' and aborts the program on failure. */ void *xrealloc(void *ptr, size_t size) { void *result = realloc(ptr, size); if (result == NULL && size > 0) alloc_failed(); return result; } /* Like `xrealloc(ptr, count * elemsize)', but aborts the program if the size is * too large. */ void *xreallocn(void *ptr, size_t count, size_t elemsize) { return xrealloc(ptr, mul(count, elemsize)); } /* Like `xrealloc(ptr, (count1 + count2) * elemsize)', but aborts the program if * the size is too large. */ void *xrealloce(void *ptr, size_t count1, size_t count2, size_t elemsize) { return xreallocn(ptr, add(count1, count2), elemsize); } /* Like `xrealloc(ptr, mainsize + count * elemsize)', but aborts the program if * the size is too large. */ void *xreallocs(void *ptr, size_t mainsize, size_t count, size_t elemsize) { return xrealloc(ptr, add(mainsize, mul(count, elemsize))); } /********** String Utilities **********/ #if !HAVE_STRNLEN extern size_t xstrnlen(const char *s, size_t maxlen) __attribute__((pure,nonnull)); #endif #if !HAVE_STRDUP extern char *xstrdup(const char *s) __attribute__((malloc,warn_unused_result,nonnull)); #endif #if !HAVE_WCSNLEN extern size_t xwcsnlen(const wchar_t *s, size_t maxlen) __attribute__((pure,nonnull)); #endif extern wchar_t *xwcsndup(const wchar_t *s, size_t maxlen) __attribute__((malloc,warn_unused_result,nonnull)); #if !HAVE_WCSDUP static inline wchar_t *xwcsdup(const wchar_t *s) __attribute__((malloc,warn_unused_result,nonnull)); #endif extern _Bool xstrtoi(const char *s, int base, int *resultp) __attribute__((warn_unused_result,nonnull)); extern _Bool xwcstoi(const wchar_t *s, int base, int *resultp) __attribute__((warn_unused_result,nonnull)); extern _Bool xwcstol(const wchar_t *s, int base, long *resultp) __attribute__((warn_unused_result,nonnull)); extern _Bool xwcstoul(const wchar_t *s, int base, unsigned long *resultp) __attribute__((warn_unused_result,nonnull)); extern char *matchstrprefix(const char *s, const char *prefix) __attribute__((pure,nonnull)); extern wchar_t *matchwcsprefix(const wchar_t *s, const wchar_t *prefix) __attribute__((pure,nonnull)); extern void *copyaswcs(const void *p) __attribute__((malloc,warn_unused_result,nonnull)); #if HAVE_STRNLEN # ifndef strnlen extern size_t strnlen(const char *s, size_t maxlen); # endif # define xstrnlen strnlen #endif #if HAVE_STRDUP # ifndef strdup extern char *strdup(const char *s); # endif # define xstrdup strdup #endif #if HAVE_WCSNLEN # ifndef wcsnlen extern size_t wcsnlen(const wchar_t *s, size_t maxlen); # endif # define xwcsnlen wcsnlen #endif #if HAVE_WCSDUP # ifndef wcsdup extern wchar_t *wcsdup(const wchar_t *s); # endif # define xwcsdup wcsdup #else /* Returns a newly malloced copy of the specified string. * Aborts the program if failed to allocate memory. */ wchar_t *xwcsdup(const wchar_t *s) { return xwcsndup(s, Size_max); } #endif /* These macros are used to cast the argument properly. * We don't need such macros for wide characters. */ #define xisalnum(c) (isalnum((unsigned char) (c))) #define xisalpha(c) (isalpha((unsigned char) (c))) #define xisblank(c) (isblank((unsigned char) (c))) #define xiscntrl(c) (iscntrl((unsigned char) (c))) #define xisdigit(c) (isdigit((unsigned char) (c))) #define xisgraph(c) (isgraph((unsigned char) (c))) #define xislower(c) (islower((unsigned char) (c))) #define xisprint(c) (isprint((unsigned char) (c))) #define xispunct(c) (ispunct((unsigned char) (c))) #define xisspace(c) (isspace((unsigned char) (c))) #define xisupper(c) (isupper((unsigned char) (c))) #define xisxdigit(c) (isxdigit((unsigned char) (c))) #define xtoupper(c) (toupper((unsigned char) (c))) #define xtolower(c) (tolower((unsigned char) (c))) /* Casts scalar to char safely. */ #define TO_CHAR(value) \ ((union { char c; unsigned char uc; }) { .uc = (unsigned char) (value), }.c) /********** Error Utilities **********/ extern const wchar_t *yash_program_invocation_name; extern const wchar_t *yash_program_invocation_short_name; extern const wchar_t *current_builtin_name; extern unsigned yash_error_message_count; extern void xerror(int errno_, const char *restrict format, ...) __attribute__((format(printf,2,3))); extern _Bool xprintf(const char *restrict format, ...) __attribute__((format(printf,1,2))); #undef Size_max #endif /* YASH_UTIL_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/variable.c000066400000000000000000002731411354143602500143600ustar00rootroot00000000000000/* Yash: yet another shell */ /* variable.c: deals with shell variables and parameters */ /* (C) 2007-2019 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "variable.h" #include #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "configm.h" #include "exec.h" #include "expand.h" #include "hashtable.h" #include "input.h" #include "option.h" #include "parser.h" #include "path.h" #include "plist.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "xfnmatch.h" #include "yash.h" #if YASH_ENABLE_LINEEDIT # include "lineedit/complete.h" # include "lineedit/lineedit.h" # include "lineedit/terminfo.h" #endif static const wchar_t *const path_variables[PA_count] = { [PA_PATH] = L VAR_PATH, [PA_CDPATH] = L VAR_CDPATH, [PA_LOADPATH] = L VAR_YASH_LOADPATH, }; /* variable environment (= set of variables) */ /* not to be confused with environment variables */ typedef struct environ_T { struct environ_T *parent; /* parent environment */ struct hashtable_T contents; /* hashtable containing variables */ bool is_temporary; /* for temporary assignment? */ char **paths[PA_count]; } environ_T; /* `contents' is a hashtable from (wchar_t *) to (variable_T *). * A variable name may contain any characters except L'\0' and L'=', though * assignment syntax disallows other characters. * Variable names starting with L'=' are used for special purposes. * The positional parameter is treated as an array whose name is L"=". * Note that the number of positional parameters is offset by 1 against the * array index. * An environment whose `is_temporary' is true is used for temporary variables. * The elements of `paths' are arrays of the pathnames contained in the * $PATH, $CDPATH and $YASH_LOADPATH variables. They are NULL if the * corresponding variables are not set. */ #define VAR_positional "=" /* flags for variable attributes */ typedef enum vartype_T { VF_SCALAR, VF_ARRAY, VF_EXPORT = 1 << 2, VF_READONLY = 1 << 3, VF_NODELETE = 1 << 4, } vartype_T; #define VF_MASK ((1 << 2) - 1) /* For any variable, the variable type is either VF_SCALAR or VF_ARRAY, * possibly OR'ed with other flags. */ /* type of variables */ typedef struct variable_T { vartype_T v_type; union { wchar_t *value; struct { void **vals; size_t valc; } array; } v_contents; void (*v_getter)(struct variable_T *var); } variable_T; #define v_value v_contents.value #define v_vals v_contents.array.vals #define v_valc v_contents.array.valc /* `v_vals' is a NULL-terminated array of pointers to wide strings. * `v_valc' is, of course, the number of elements in `v_vals'. * `v_value', `v_vals' and the elements of `v_vals' are `free'able. * `v_value' is NULL if the variable is declared but not yet assigned. * `v_vals' is always non-NULL, but it may contain no elements. * `v_getter' is the setter function, which is reset to NULL on reassignment.*/ /* type of shell functions (defined later) */ typedef struct function_T function_T; static void varvaluefree(variable_T *v) __attribute__((nonnull)); static void varfree(variable_T *v); static void varkvfree(kvpair_T kv); static void varkvfree_reexport(kvpair_T kv); static void init_pwd(void); static variable_T *search_variable(const wchar_t *name) __attribute__((pure,nonnull)); static variable_T *search_array_and_check_if_changeable(const wchar_t *name) __attribute__((pure,nonnull)); static void update_environment(const wchar_t *name) __attribute__((nonnull)); static void reset_locale(const wchar_t *name) __attribute__((nonnull)); static void reset_locale_category(const wchar_t *name, int category) __attribute__((nonnull)); static variable_T *new_global(const wchar_t *name) __attribute__((nonnull)); static variable_T *new_local(const wchar_t *name) __attribute__((nonnull)); static variable_T *new_temporary(const wchar_t *name) __attribute__((nonnull)); static variable_T *new_variable(const wchar_t *name, scope_T scope) __attribute__((nonnull)); static void xtrace_variable(const wchar_t *name, const wchar_t *value) __attribute__((nonnull)); static void xtrace_array(const wchar_t *name, void *const *values) __attribute__((nonnull)); static size_t make_array_of_all_variables(bool global, kvpair_T **resultp) __attribute__((nonnull)); static void get_all_variables_rec( hashtable_T *table, environ_T *env, bool global) __attribute__((nonnull)); static void lineno_getter(variable_T *var) __attribute__((nonnull)); static void random_getter(variable_T *var) __attribute__((nonnull)); static unsigned next_random(void); static void variable_set(const wchar_t *name, variable_T *var) __attribute__((nonnull(1))); static char **convert_path_array(void **ary) __attribute__((malloc,warn_unused_result)); static void add_to_list_no_dup(plist_T *list, char *s) __attribute__((nonnull(1))); static void reset_path(path_T name, variable_T *var); static void funcfree(function_T *f); static void funckvfree(kvpair_T kv); static void hash_all_commands_recursively(const command_T *c); static void hash_all_commands_in_and_or(const and_or_T *ao); static void hash_all_commands_in_if(const ifcommand_T *ic); static void hash_all_commands_in_case(const caseitem_T *ci); static void tryhash_word_as_command(const wordunit_T *w); /* the current environment */ static environ_T *current_env; /* the top-level environment (the farthest from the current) */ static environ_T *first_env; /* whether $RANDOM is functioning as a random number */ static bool random_active; /* hashtable from function names (wchar_t *) to functions (function_T *). */ static hashtable_T functions; /* Frees the value of the specified variable (but not the variable itself). */ /* This function does not change the value of `*v'. */ void varvaluefree(variable_T *v) { switch (v->v_type & VF_MASK) { case VF_SCALAR: free(v->v_value); break; case VF_ARRAY: plfree(v->v_vals, free); break; } } /* Frees the specified variable. */ void varfree(variable_T *v) { if (v != NULL) { varvaluefree(v); free(v); } } /* Frees the specified key-value pair of a variable name and a variable. */ void varkvfree(kvpair_T kv) { free(kv.key); varfree(kv.value); } /* Calls `variable_set' and `update_environment' for `kv.key' and * calls `varkvfree'. */ void varkvfree_reexport(kvpair_T kv) { if (kv.key == NULL) { assert(kv.value == NULL); return; } variable_set(kv.key, NULL); if (((variable_T *) kv.value)->v_type & VF_EXPORT) update_environment(kv.key); varkvfree(kv); } /* Initializes the top-level environment. */ void init_environment(void) { assert(first_env == NULL && current_env == NULL); first_env = current_env = xmalloc(sizeof *current_env); current_env->parent = NULL; current_env->is_temporary = false; ht_init(¤t_env->contents, hashwcs, htwcscmp); // for (size_t i = 0; i < PA_count; i++) // current_env->paths[i] = NULL; ht_init(&functions, hashwcs, htwcscmp); /* add all the existing environment variables to the variable environment */ for (char **e = environ; *e != NULL; e++) { wchar_t *we = malloc_mbstowcs(*e); if (we == NULL) continue; wchar_t *eqp = wcschr(we, L'='); variable_T *v = xmalloc(sizeof *v); v->v_type = VF_SCALAR | VF_EXPORT; v->v_value = (eqp != NULL) ? xwcsdup(&eqp[1]) : NULL; v->v_getter = NULL; if (eqp != NULL) { *eqp = L'\0'; we = xreallocn(we, eqp - we + 1, sizeof *we); } varkvfree(ht_set(¤t_env->contents, we, v)); } /* initialize path according to $PATH etc. */ for (size_t i = 0; i < PA_count; i++) current_env->paths[i] = decompose_paths(getvar(path_variables[i])); } /* Initializes the default variables. * This function must be called after the shell options have been set. */ void init_variables(void) { /* set $IFS */ set_variable(L VAR_IFS, xwcsdup(DEFAULT_IFS), SCOPE_GLOBAL, false); /* set $LINENO */ { variable_T *v = new_variable(L VAR_LINENO, SCOPE_GLOBAL); assert(v != NULL); v->v_type = VF_SCALAR | (v->v_type & VF_EXPORT); v->v_value = NULL; v->v_getter = lineno_getter; // variable_set(VAR_LINENO, v); if (v->v_type & VF_EXPORT) update_environment(L VAR_LINENO); } /* set $MAILCHECK */ if (getvar(L VAR_MAILCHECK) == NULL) set_variable(L VAR_MAILCHECK, xwcsdup(L"600"), SCOPE_GLOBAL, false); /* set $PS1~4 */ { const wchar_t *ps1 = (geteuid() != 0) ? L"$ " : L"# "; set_variable(L VAR_PS1, xwcsdup(ps1), SCOPE_GLOBAL, false); set_variable(L VAR_PS2, xwcsdup(L"> "), SCOPE_GLOBAL, false); set_variable(L VAR_PS4, xwcsdup(L"+ "), SCOPE_GLOBAL, false); } /* set $PWD */ init_pwd(); /* export $OLDPWD */ { variable_T *v = new_global(L VAR_OLDPWD); assert(v != NULL); v->v_type |= VF_EXPORT; variable_set(L VAR_OLDPWD, v); } /* set $PPID */ set_variable(L VAR_PPID, malloc_wprintf(L"%jd", (intmax_t) getppid()), SCOPE_GLOBAL, false); /* set $OPTIND */ set_variable(L VAR_OPTIND, xwcsdup(L"1"), SCOPE_GLOBAL, false); /* set $RANDOM */ if (!posixly_correct) { variable_T *v = new_variable(L VAR_RANDOM, SCOPE_GLOBAL); assert(v != NULL); v->v_type = VF_SCALAR; v->v_value = NULL; v->v_getter = random_getter; random_active = true; srand((unsigned) time(NULL) ^ (unsigned) shell_pid << 17); } else { random_active = false; } /* set $YASH_LOADPATH */ if (getvar(L VAR_YASH_LOADPATH) == NULL) set_variable(L VAR_YASH_LOADPATH, xwcsdup(L DEFAULT_LOADPATH), SCOPE_GLOBAL, false); /* set $YASH_VERSION */ set_variable(L VAR_YASH_VERSION, xwcsdup(L PACKAGE_VERSION), SCOPE_GLOBAL, false); } /* Reset the value of $PWD if * - $PWD is not set, or * - the value of $PWD isn't an absolute path, or * - the value of $PWD isn't the actual current directory, or * - the value of $PWD isn't canonicalized. */ void init_pwd(void) { const char *pwd = getenv(VAR_PWD); if (pwd == NULL || pwd[0] != '/' || !is_same_file(pwd, ".")) goto set; const wchar_t *wpwd = getvar(L VAR_PWD); if (wpwd == NULL || !is_normalized_path(wpwd)) goto set; return; char *newpwd; wchar_t *wnewpwd; set: newpwd = xgetcwd(); if (newpwd == NULL) { xerror(errno, Ngt("failed to set $PWD")); return; } wnewpwd = realloc_mbstowcs(newpwd); if (wnewpwd == NULL) { xerror(0, Ngt("failed to set $PWD")); return; } set_variable(L VAR_PWD, wnewpwd, SCOPE_GLOBAL, true); } /* Searches for a variable with the specified name. * Returns NULL if none was found. */ variable_T *search_variable(const wchar_t *name) { for (environ_T *env = current_env; env != NULL; env = env->parent) { variable_T *var = ht_get(&env->contents, name).value; if (var != NULL) return var; } return NULL; } /* Searches for an array with the specified name and checks if it is not read- * only. If unsuccessful, prints an error message and returns NULL. */ variable_T *search_array_and_check_if_changeable(const wchar_t *name) { variable_T *array = search_variable(name); if (array == NULL || (array->v_type & VF_MASK) != VF_ARRAY) { xerror(0, Ngt("no such array $%ls"), name); return NULL; } else if (array->v_type & VF_READONLY) { xerror(0, Ngt("$%ls is read-only"), name); return NULL; } return array; } /* Update the value in `environ' for the variable with the specified name. * `name' must not contain '='. */ void update_environment(const wchar_t *name) { char *mname = malloc_wcstombs(name); if (mname == NULL) return; char *value = get_exported_value(name); if (value == NULL) { if (xunsetenv(mname) < 0) xerror(errno, Ngt("failed to unset environment variable $%s"), mname); } else { if (setenv(mname, value, true) < 0) xerror(errno, Ngt("failed to set environment variable $%s"), mname); } free(mname); free(value); } /* Returns the value of variable `name' that should be exported. * If the variable is not exported or the variable value cannot be converted to * a multibyte string, NULL is returned. */ char *get_exported_value(const wchar_t *name) { for (environ_T *env = current_env; env != NULL; env = env->parent) { const variable_T *var = ht_get(&env->contents, name).value; if (var != NULL && (var->v_type & VF_EXPORT)) { switch (var->v_type & VF_MASK) { case VF_SCALAR: if (var->v_value == NULL) continue; return malloc_wcstombs(var->v_value); case VF_ARRAY: return realloc_wcstombs(joinwcsarray(var->v_vals, L":")); default: assert(false); } } } return NULL; } /* Resets the locate settings for the specified variable. * If `name' is not any of "LANG", "LC_ALL", etc., does nothing. */ void reset_locale(const wchar_t *name) { if (wcscmp(name, L VAR_LANG) == 0) { goto reset_locale_all; } else if (wcsncmp(name, L"LC_", 3) == 0) { /* POSIX forbids resetting LC_CTYPE even if the value of the variable * is changed, but we do reset LC_CTYPE if the shell is interactive and * not in the POSIXly-correct mode. */ if (wcscmp(&name[3], &(L VAR_LC_ALL)[3]) == 0) { reset_locale_all: reset_locale_category(L VAR_LC_COLLATE, LC_COLLATE); if (!posixly_correct && is_interactive_now) reset_locale_category(L VAR_LC_CTYPE, LC_CTYPE); reset_locale_category(L VAR_LC_MESSAGES, LC_MESSAGES); reset_locale_category(L VAR_LC_MONETARY, LC_MONETARY); reset_locale_category(L VAR_LC_NUMERIC, LC_NUMERIC); reset_locale_category(L VAR_LC_TIME, LC_TIME); } else if (wcscmp(&name[3], &(L VAR_LC_COLLATE)[3]) == 0) { reset_locale_category(L VAR_LC_COLLATE, LC_COLLATE); } else if (wcscmp(&name[3], &(L VAR_LC_CTYPE)[3]) == 0) { if (!posixly_correct && is_interactive_now) reset_locale_category(L VAR_LC_CTYPE, LC_CTYPE); } else if (wcscmp(&name[3], &(L VAR_LC_MESSAGES)[3]) == 0) { reset_locale_category(L VAR_LC_MESSAGES, LC_MESSAGES); } else if (wcscmp(&name[3], &(L VAR_LC_MONETARY)[3]) == 0) { reset_locale_category(L VAR_LC_MONETARY, LC_MONETARY); } else if (wcscmp(&name[3], &(L VAR_LC_NUMERIC)[3]) == 0) { reset_locale_category(L VAR_LC_NUMERIC, LC_NUMERIC); } else if (wcscmp(&name[3], &(L VAR_LC_TIME)[3]) == 0) { reset_locale_category(L VAR_LC_TIME, LC_TIME); } } } /* Resets the locale of the specified category. * `name' must be one of the `LC_*' constants except LC_ALL. */ void reset_locale_category(const wchar_t *name, int category) { const wchar_t *locale = getvar(L VAR_LC_ALL); if (locale == NULL) { locale = getvar(name); if (locale == NULL) { locale = getvar(L VAR_LANG); if (locale == NULL) locale = L""; } } char *wlocale = malloc_wcstombs(locale); if (wlocale != NULL) { setlocale(category, wlocale); free(wlocale); } } /* Creates a new scalar variable that has no value. * If the variable already exists, it is returned without change. So the return * value may be an array variable or it may be a scalar variable with a value. * Temporary variables with the `name' are cleared if any. */ variable_T *new_global(const wchar_t *name) { variable_T *var; for (environ_T *env = current_env; env != NULL; env = env->parent) { var = ht_get(&env->contents, name).value; if (var != NULL) { if (env->is_temporary) { assert(!(var->v_type & VF_NODELETE)); varkvfree_reexport(ht_remove(&env->contents, name)); continue; } return var; } } var = xmalloc(sizeof *var); var->v_type = VF_SCALAR; var->v_value = NULL; var->v_getter = NULL; ht_set(&first_env->contents, xwcsdup(name), var); return var; } /* Creates a new scalar variable that has no value. * If the variable already exists, it is returned without change. So the return * value may be an array variable or it may be a scalar variable with a value. * Temporary variables with the `name' are cleared if any. */ variable_T *new_local(const wchar_t *name) { environ_T *env = current_env; while (env->is_temporary) { varkvfree_reexport(ht_remove(&env->contents, name)); env = env->parent; } variable_T *var = ht_get(&env->contents, name).value; if (var != NULL) return var; var = xmalloc(sizeof *var); var->v_type = VF_SCALAR; var->v_value = NULL; var->v_getter = NULL; ht_set(&env->contents, xwcsdup(name), var); return var; } /* Creates a new scalar variable that has no value. * If the variable already exists, it is returned without change. So the return * value may be an array variable or it may be a scalar variable with a value. * The current environment must be a temporary environment. * If there is a read-only non-temporary variable with the specified name, it is * returned (no new temporary variable is created). */ variable_T *new_temporary(const wchar_t *name) { environ_T *env = current_env; assert(env->is_temporary); /* check if read-only */ variable_T *var = search_variable(name); if (var != NULL && (var->v_type & VF_READONLY)) return var; var = ht_get(&env->contents, name).value; if (var != NULL) return var; var = xmalloc(sizeof *var); var->v_type = VF_SCALAR; var->v_value = NULL; var->v_getter = NULL; ht_set(&env->contents, xwcsdup(name), var); return var; } /* Creates a new variable with the specified name if there is none. * If the variable already exists, it is cleared and returned. * * On error, an error message is printed to the standard error and NULL is * returned. Otherwise, the (new) variable is returned. * `v_type' is the only valid member of the returned variable and all the * members of the variable (including `v_type') must be initialized by the * caller. If `v_type' of the return value includes the VF_EXPORT flag, the * caller must call `update_environment'. */ variable_T *new_variable(const wchar_t *name, scope_T scope) { variable_T *var; switch (scope) { case SCOPE_GLOBAL: var = new_global(name); break; case SCOPE_LOCAL: var = new_local(name); break; case SCOPE_TEMP: var = new_temporary(name); break; default: assert(false); } if (var->v_type & VF_READONLY) { xerror(0, Ngt("$%ls is read-only"), name); return NULL; } else { varvaluefree(var); return var; } } /* Creates a scalar variable with the specified name and value. * `value' must be a `free'able string or NULL. The caller must not modify or * free `value' hereafter, whether or not this function is successful. * If `export' is true, the variable is exported (i.e., the VF_EXPORT flag is * set to the variable). But this function does not reset an existing VF_EXPORT * flag if `export' is false. * Returns true iff successful. On error, an error message is printed to the * standard error. */ bool set_variable( const wchar_t *name, wchar_t *value, scope_T scope, bool export) { variable_T *var = new_variable(name, scope); if (var == NULL) { free(value); return false; } var->v_type = VF_SCALAR | (var->v_type & (VF_EXPORT | VF_NODELETE)) | (export ? VF_EXPORT : 0); var->v_value = value; var->v_getter = NULL; variable_set(name, var); if (var->v_type & VF_EXPORT) update_environment(name); return true; } /* Creates an array variable with the specified name and values. * `values' is a NULL-terminated array of pointers to wide strings. It is used * as the contents of the array variable hereafter, so you must not modify or * free the array or its elements whether or not this function succeeds. * `values' and its elements must be `free'able. * `count' is the number of elements in `values'. If `count' is zero, the * number is counted in this function. * If `export' is true, the variable is exported (i.e., the VF_EXPORT flag is * set to the variable). But this function does not reset an existing VF_EXPORT * flag if `export' is false. * Returns the set array iff successful. On error, an error message is printed * to the standard error and NULL is returned. */ variable_T *set_array(const wchar_t *name, size_t count, void **values, scope_T scope, bool export) { variable_T *var = new_variable(name, scope); if (var == NULL) { plfree(values, free); return NULL; } var->v_type = VF_ARRAY | (var->v_type & (VF_EXPORT | VF_NODELETE)) | (export ? VF_EXPORT : 0); var->v_vals = values; var->v_valc = (count != 0) ? count : plcount(var->v_vals); var->v_getter = NULL; variable_set(name, var); if (var->v_type & VF_EXPORT) update_environment(name); return var; } /* Changes the value of the specified array element. * `name' must be the name of an existing array. * `index' is the index of the element (counted from zero). * `value' is the new value, which must be a `free'able string. Since `value' is * used as the contents of the array element, you must not modify or free * `value' after this function returned (whether successful or not). * Returns true iff successful. An error message is printed on failure. */ bool set_array_element(const wchar_t *name, size_t index, wchar_t *value) { variable_T *array = search_array_and_check_if_changeable(name); if (array == NULL) goto fail; if (array->v_valc <= index) goto invalid_index; free(array->v_vals[index]); array->v_vals[index] = value; if (array->v_type & VF_EXPORT) update_environment(name); return true; invalid_index: xerror(0, Ngt("index %zu is out of range " "(the actual size of array $%ls is %zu)"), index + 1, name, array->v_valc); fail: free(value); return false; } /* Sets the positional parameters of the current environment. * The existent parameters are cleared. * `values' is an NULL-terminated array of pointers to wide strings. * `values[0]' will be the new $1, `values[1]' $2, and so on. * When a new non-temporary environment is created, this function must be called * at least once before the environment is used by the user. */ void set_positional_parameters(void *const *values) { set_array(L VAR_positional, 0, pldup(values, copyaswcs), SCOPE_LOCAL, false); } /* Performs the specified assignments. * If `shopt_xtrace' is true, traces are printed to the standard error. * If `temp' is true, the variables are assigned in the current environment, * which must be a temporary environment. Otherwise, they are assigned globally. * If `export' is true, the variables are exported (i.e., the VF_EXPORT flag is * set to the variables). But this function does not reset any existing * VF_EXPORT flag if `export' is false. * Returns true iff successful. On error, already-assigned variables are not * restored to the previous values. */ bool do_assignments(const assign_T *assign, bool temp, bool export) { if (temp) assert(current_env->is_temporary); scope_T scope = temp ? SCOPE_TEMP : SCOPE_GLOBAL; while (assign != NULL) { wchar_t *value; int count; void **values; switch (assign->a_type) { case A_SCALAR: value = expand_single_and_unescape( assign->a_scalar, TT_MULTI, true, false); if (value == NULL) return false; if (shopt_xtrace) xtrace_variable(assign->a_name, value); if (!set_variable(assign->a_name, value, scope, export)) return false; break; case A_ARRAY: if (!expand_line(assign->a_array, &count, &values)) return false; assert(values != NULL); if (shopt_xtrace) xtrace_array(assign->a_name, values); if (!set_array(assign->a_name, count, values, scope, export)) return false; break; } assign = assign->next; } return true; } /* Pushes a trace of the specified variable assignment to the xtrace buffer. */ void xtrace_variable(const wchar_t *name, const wchar_t *value) { xwcsbuf_T *buf = get_xtrace_buffer(); wb_wccat(buf, L' '); wb_cat(buf, name); wb_wccat(buf, L'='); wb_quote_as_word(buf, value); } /* Pushes a trace of the specified array assignment to the xtrace buffer. */ void xtrace_array(const wchar_t *name, void *const *values) { xwcsbuf_T *buf = get_xtrace_buffer(); wb_wprintf(buf, L" %ls=(", name); if (*values != NULL) { for (;;) { wb_quote_as_word(buf, *values); values++; if (*values == NULL) break; wb_wccat(buf, L' '); } } wb_wccat(buf, L')'); } /* Gets the value of the specified scalar variable. * Cannot be used for special parameters such as $$ and $@. * Returns the value of the variable, or NULL if not found. * The return value must not be modified or `free'ed by the caller and * is valid until the variable is re-assigned or unset. */ const wchar_t *getvar(const wchar_t *name) { variable_T *var = search_variable(name); if (var != NULL && (var->v_type & VF_MASK) == VF_SCALAR) { if (var->v_getter) { var->v_getter(var); if ((var->v_type & VF_MASK) != VF_SCALAR) return NULL; } return var->v_value; } return NULL; } /* Returns the value(s) of the specified variable/array as an array. * The return value's type is `struct get_variable_T'. It has three members: * `type', `count' and `values'. * `type' is the type of the result: * GV_NOTFOUND: no such variable/array * GV_SCALAR: a normal scalar variable * GV_ARRAY: an array of zero or more values * GV_ARRAY_CONCAT: an array whose values should be concatenated by caller * `values' is an array containing the value(s) of the variable/array. * A scalar value (GV_SCALAR) is returned as a NULL-terminated array containing * exactly one wide string. An array (GV_ARRAY*) is returned as a NULL- * terminated array of pointers to wide strings. If no such variable is found * (GV_NOTFOUND), `values' is NULL. The caller must free the `values' array and * its element strings iff `freevalues' is true. If `freevalues' is false, the * caller must not modify the array or its elements. * `count' is the number of elements in `values'. */ struct get_variable_T get_variable(const wchar_t *name) { struct get_variable_T result; wchar_t *value; variable_T *var; if (name[0] == L'\0') { goto not_found; } else if (name[1] == L'\0') { /* `name' is one-character long: check if it's a special parameter */ switch (name[0]) { case L'*': result.type = GV_ARRAY_CONCAT; goto positional_parameters; case L'@': result.type = GV_ARRAY; positional_parameters: var = search_variable(L VAR_positional); assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); result.count = var->v_valc; result.values = var->v_vals; result.freevalues = false; return result; case L'#': var = search_variable(L VAR_positional); assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); value = malloc_wprintf(L"%zu", var->v_valc); goto return_single; case L'?': value = malloc_wprintf(L"%d", laststatus); goto return_single; case L'-': value = get_hyphen_parameter(); goto return_single; case L'$': value = malloc_wprintf(L"%jd", (intmax_t) shell_pid); goto return_single; case L'!': value = malloc_wprintf(L"%jd", (intmax_t) lastasyncpid); goto return_single; case L'0': value = xwcsdup(command_name); goto return_single; } } if (iswdigit(name[0])) { /* `name' starts with a digit: a positional parameter */ wchar_t *nameend; errno = 0; uintmax_t v = wcstoumax(name, &nameend, 10); if (errno != 0 || *nameend != L'\0') goto not_found; /* not a number or overflow */ var = search_variable(L VAR_positional); assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); if (v == 0 || var->v_valc < v) goto not_found; /* index out of bounds */ value = xwcsdup(var->v_vals[v - 1]); goto return_single; } /* now it should be a normal variable */ var = search_variable(name); if (var != NULL) { if (var->v_getter) var->v_getter(var); switch (var->v_type & VF_MASK) { case VF_SCALAR: value = var->v_value ? xwcsdup(var->v_value) : NULL; goto return_single; case VF_ARRAY: result.type = GV_ARRAY; result.count = var->v_valc; result.values = var->v_vals; result.freevalues = false; return result; } } goto not_found; return_single: /* return a scalar as a one-element array */ if (value != NULL) { result.type = GV_SCALAR; result.count = 1; result.values = xmallocn(2, sizeof *result.values); result.values[0] = value; result.values[1] = NULL; result.freevalues = true; return result; } not_found: return (struct get_variable_T) { .type = GV_NOTFOUND }; } /* If `gv->freevalues' is false, substitutes `gv->values' with a newly-malloced * copy of it and turns `gv->freevalues' to true. */ void save_get_variable_values(struct get_variable_T *gv) { if (!gv->freevalues) { gv->values = plndup(gv->values, gv->count, copyaswcs); gv->freevalues = true; } } /* Makes a new array that contains all the variables in the current environment. * The elements of the array are key-value pairs of names (const wchar_t *) and * values (const variable_T *). * If `global' is true, variables in all the ancestor environments are also * included (except the ones hidden by local variables). * The resultant array is assigned to `*resultp' and the number of the key-value * pairs is returned. The array contents must not be modified or freed. */ size_t make_array_of_all_variables(bool global, kvpair_T **resultp) { if (current_env->parent == NULL || (!global && current_env->is_temporary)) { *resultp = ht_tokvarray(¤t_env->contents); return current_env->contents.count; } else { hashtable_T variables; size_t count; ht_init(&variables, hashwcs, htwcscmp); get_all_variables_rec(&variables, current_env, global); *resultp = ht_tokvarray(&variables); count = variables.count; ht_destroy(&variables); return count; } } /* Gathers all variables in the specified environment and adds them to the * specified hashtable. * If `global' is true, variables in all the ancestor environments are also * included (except the ones hidden by local variables). * Keys and values added to the hashtable must not be modified or freed by the * caller. */ void get_all_variables_rec(hashtable_T *table, environ_T *env, bool global) { if (env->parent != NULL && (global || env->is_temporary)) get_all_variables_rec(table, env->parent, global); size_t i = 0; kvpair_T kv; while ((kv = ht_next(&env->contents, &i)).key != NULL) ht_set(table, kv.key, kv.value); } /* Creates a new variable environment. * `temp' specifies whether the new environment is for temporary assignments. * The current environment will be the parent of the new environment. */ /* Don't forget to call `set_positional_parameters'! */ void open_new_environment(bool temp) { environ_T *newenv = xmalloc(sizeof *newenv); newenv->parent = current_env; newenv->is_temporary = temp; ht_init(&newenv->contents, hashwcs, htwcscmp); for (size_t i = 0; i < PA_count; i++) newenv->paths[i] = NULL; current_env = newenv; } /* Destroys the current variable environment. * The parent of the current becomes the new current. */ void close_current_environment(void) { environ_T *oldenv = current_env; assert(oldenv != first_env); current_env = oldenv->parent; ht_clear(&oldenv->contents, varkvfree_reexport); ht_destroy(&oldenv->contents); for (size_t i = 0; i < PA_count; i++) plfree((void **) oldenv->paths[i], free); free(oldenv); } /********** Getters **********/ /* line number of the currently executing command */ unsigned long current_lineno; /* getter for $LINENO */ void lineno_getter(variable_T *var) { assert((var->v_type & VF_MASK) == VF_SCALAR); free(var->v_value); var->v_value = malloc_wprintf(L"%lu", current_lineno); // variable_set(VAR_LINENO, var); if (var->v_type & VF_EXPORT) update_environment(L VAR_LINENO); } /* getter for $RANDOM */ void random_getter(variable_T *var) { assert((var->v_type & VF_MASK) == VF_SCALAR); free(var->v_value); var->v_value = malloc_wprintf(L"%u", next_random()); // variable_set(VAR_RANDOM, var); if (var->v_type & VF_EXPORT) update_environment(L VAR_RANDOM); } /* Returns a random number between 0 and 32767 using `rand'. */ unsigned next_random(void) { #if (RAND_MAX & (RAND_MAX + 1u)) == 0u // RAND_MAX + 1 is a power of 2 unsigned v = (unsigned) rand(); return (v ^ (v >> 15)) & ((1u << 15) - 1u); #else unsigned v; do v = (unsigned) rand(); while (v > RAND_MAX - (RAND_MAX + 1u) % (1u << 15)); return v & ((1u << 15) - 1u); #endif } /********** Setter **********/ /* General callback function that is called after an assignment. * `var' is NULL when the variable is unset. */ void variable_set(const wchar_t *name, variable_T *var) { switch (name[0]) { case L'C': if (wcscmp(name, L VAR_CDPATH) == 0) reset_path(PA_CDPATH, var); #if YASH_ENABLE_LINEEDIT else if (wcscmp(name, L VAR_COLUMNS) == 0) le_need_term_update = true; #endif break; case L'L': if (wcscmp(name, L VAR_LANG) == 0 || wcsncmp(name, L"LC_", 3) == 0) reset_locale(name); #if YASH_ENABLE_LINEEDIT else if (wcscmp(name, L VAR_LINES) == 0) le_need_term_update = true; #endif break; case L'P': if (wcscmp(name, L VAR_PATH) == 0) { clear_cmdhash(); reset_path(PA_PATH, var); } break; case L'R': if (random_active && wcscmp(name, L VAR_RANDOM) == 0) { random_active = false; if (var != NULL && (var->v_type & VF_MASK) == VF_SCALAR && var->v_value != NULL) { unsigned long seed; if (xwcstoul(var->v_value, 0, &seed)) { srand((unsigned) seed); var->v_getter = random_getter; random_active = true; } } } break; #if YASH_ENABLE_LINEEDIT case L'T': if (wcscmp(name, L VAR_TERM) == 0) le_need_term_update = true; break; #endif /* YASH_ENABLE_LINEEDIT */ case L'Y': if (wcscmp(name, L VAR_YASH_LOADPATH) == 0) reset_path(PA_LOADPATH, var); break; } } /********** Path Array Manipulation **********/ /* Splits the specified string at colons. * If `paths' is non-NULL, a newly-malloced NULL-terminated array of pointers to * newly-malloced multibyte strings is returned. * If `paths' is NULL, NULL is returned. */ char **decompose_paths(const wchar_t *paths) { if (paths == NULL) return NULL; plist_T list; pl_init(&list); const wchar_t *colon; while ((colon = wcschr(paths, L':')) != NULL) { add_to_list_no_dup(&list, malloc_wcsntombs(paths, colon - paths)); paths = &colon[1]; } add_to_list_no_dup(&list, malloc_wcstombs(paths)); return (char **) pl_toary(&list); } /* Converts an array of wide strings into an newly-malloced array of multibyte * strings. * If `paths' is NULL, NULL is returned. */ char **convert_path_array(void **ary) { if (ary == NULL) return NULL; plist_T list; pl_init(&list); while (*ary != NULL) { add_to_list_no_dup(&list, malloc_wcstombs(*ary)); ary++; } return (char **) pl_toary(&list); } /* If `s' is non-NULL and not contained in `list', adds `s' to list. * Otherwise, frees `s'. */ void add_to_list_no_dup(plist_T *list, char *s) { if (s != NULL) { for (size_t i = 0; i < list->length; i++) { if (strcmp(s, list->contents[i]) == 0) { free(s); return; } } pl_add(list, s); } } /* Reconstructs the path array of the specified variable in the environment. * `var' may be NULL. */ void reset_path(path_T name, variable_T *var) { for (environ_T *env = current_env; env != NULL; env = env->parent) { plfree((void **) env->paths[name], free); variable_T *v = ht_get(&env->contents, path_variables[name]).value; if (v != NULL) { switch (v->v_type & VF_MASK) { case VF_SCALAR: env->paths[name] = decompose_paths(v->v_value); break; case VF_ARRAY: env->paths[name] = convert_path_array(v->v_vals); break; } if (v == var) break; } else { env->paths[name] = NULL; } } } /* Returns the path array of the specified variable. * The return value is NULL if the variable is not set. * The caller must not make any change to the returned array. */ char *const *get_path_array(path_T name) { for (environ_T *env = current_env; env != NULL; env = env->parent) if (env->paths[name] != NULL) return env->paths[name]; return NULL; } /********** Shell Functions **********/ /* type of functions */ struct function_T { vartype_T f_type; /* only VF_READONLY and VF_NODELETE are valid */ command_T *f_body; /* body of function */ }; /* Frees the specified function. */ void funcfree(function_T *f) { if (f != NULL) { comsfree(f->f_body); free(f); } } /* Frees the specified key-value pair of a function name and a function. */ void funckvfree(kvpair_T kv) { free(kv.key); funcfree(kv.value); } /* Defines function `name' as command `body'. * It is an error to re-define a read-only function. * Returns true iff successful. */ bool define_function(const wchar_t *name, command_T *body) { function_T *f = ht_get(&functions, name).value; if (f != NULL && (f->f_type & VF_READONLY)) { xerror(0, Ngt("function `%ls' cannot be redefined " "because it is read-only"), name); return false; } f = xmalloc(sizeof *f); f->f_type = 0; f->f_body = comsdup(body); if (shopt_hashondef) hash_all_commands_recursively(body); funckvfree(ht_set(&functions, xwcsdup(name), f)); return true; } /* Gets the body of the function with the specified name. * Returns NULL if there is no such a function. */ command_T *get_function(const wchar_t *name) { function_T *f = ht_get(&functions, name).value; if (f != NULL) return f->f_body; else return NULL; } /* Registers all the commands in the argument to the command hashtable. */ void hash_all_commands_recursively(const command_T *c) { for (; c != NULL; c = c->next) { switch (c->c_type) { case CT_SIMPLE: if (c->c_words) tryhash_word_as_command(c->c_words[0]); break; case CT_GROUP: case CT_SUBSHELL: hash_all_commands_in_and_or(c->c_subcmds); break; case CT_IF: hash_all_commands_in_if(c->c_ifcmds); break; case CT_FOR: hash_all_commands_in_and_or(c->c_forcmds); break; case CT_WHILE: hash_all_commands_in_and_or(c->c_whlcond); hash_all_commands_in_and_or(c->c_whlcmds); break; case CT_CASE: hash_all_commands_in_case(c->c_casitems); break; #if YASH_ENABLE_DOUBLE_BRACKET case CT_BRACKET: #endif case CT_FUNCDEF: break; } } } void hash_all_commands_in_and_or(const and_or_T *ao) { for (; ao != NULL; ao = ao->next) { for (pipeline_T *p = ao->ao_pipelines; p != NULL; p = p->next) { hash_all_commands_recursively(p->pl_commands); } } } void hash_all_commands_in_if(const ifcommand_T *ic) { for (; ic != NULL; ic = ic->next) { hash_all_commands_in_and_or(ic->ic_condition); hash_all_commands_in_and_or(ic->ic_commands); } } void hash_all_commands_in_case(const caseitem_T *ci) { for (; ci != NULL; ci = ci->next) { hash_all_commands_in_and_or(ci->ci_commands); } } void tryhash_word_as_command(const wordunit_T *w) { if (w != NULL && w->next == NULL && w->wu_type == WT_STRING) { wchar_t *cmdname = unquote(w->wu_string); if (wcschr(cmdname, L'/') == NULL) { char *mbsname = malloc_wcstombs(cmdname); get_command_path(mbsname, false); free(mbsname); } free(cmdname); } } #if YASH_ENABLE_LINEEDIT /* Generates completion candidates for variable names matching the pattern. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_variable_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_VARIABLE)) return; le_compdebug("adding variable name candidates"); if (!le_compile_cpatterns(compopt)) return; size_t i = 0; kvpair_T kv; while ((kv = ht_next(&first_env->contents, &i)).key != NULL) { const wchar_t *name = kv.key; const variable_T *var = kv.value; switch (var->v_type & VF_MASK) { case VF_SCALAR: if (!(compopt->type & CGT_SCALAR)) continue; break; case VF_ARRAY: if (!(compopt->type & CGT_ARRAY)) continue; break; } if (name[0] != L'=' && le_wmatch_comppatterns(compopt, name)) le_new_candidate(CT_VAR, xwcsdup(name), NULL, compopt); } } /* Generates completion candidates for function names matching the pattern. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_function_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_FUNCTION)) return; le_compdebug("adding function name candidates"); if (!le_compile_cpatterns(compopt)) return; size_t i = 0; const wchar_t *name; while ((name = ht_next(&functions, &i).key) != NULL) if (le_wmatch_comppatterns(compopt, name)) le_new_candidate(CT_COMMAND, xwcsdup(name), NULL, compopt); } #endif /* YASH_ENABLE_LINEEDIT */ /********** Directory Stack **********/ #if YASH_ENABLE_DIRSTACK /* Parses the specified string as the index of a directory stack entry. * The index string must start with the plus or minus sign. * If the corresponding entry is found, the entry is assigned to `*entryp' and * the index value is assigned to `*indexp'. If the index is out of range, it is * an error. If the string is not a valid integer, `indexstr' is assigned to * `*entryp' and SIZE_MAX is assigned to `*indexp'. * If the index denotes $PWD, the number of the entries in $DIRSTACK is assigned * to `*indexp'. * Returns true if successful (`*entryp' and `*indexp' are assigned). * Returns false on error, in which case an error message is printed iff * `printerror' is true. */ bool parse_dirstack_index( const wchar_t *restrict indexstr, size_t *restrict indexp, const wchar_t **restrict entryp, bool printerror) { long num; if (indexstr[0] != L'-' && indexstr[0] != L'+') goto not_index; if (!xwcstol(indexstr, 10, &num)) goto not_index; variable_T *var = search_variable(L VAR_DIRSTACK); if (var == NULL || ((var->v_type & VF_MASK) != VF_ARRAY)) { if (num == 0) goto return_pwd; if (printerror) xerror(0, Ngt("the directory stack is empty")); return false; } #if LONG_MAX > SIZE_MAX if (num > SIZE_MAX || num < -(long) SIZE_MAX) goto out_of_range; #endif if (indexstr[0] == L'+' && num >= 0) { if (num == 0) { num = var->v_valc; goto return_pwd; } if ((size_t) num > var->v_valc) goto out_of_range; *indexp = var->v_valc - (size_t) num; *entryp = var->v_vals[*indexp]; return true; } else if (indexstr[0] == L'-' && num <= 0) { if (num == LONG_MIN) goto out_of_range; num = -num; assert(num >= 0); if ((size_t) num > var->v_valc) goto out_of_range; if ((size_t) num == var->v_valc) goto return_pwd; *indexp = (size_t) num; *entryp = var->v_vals[*indexp]; return true; } else { goto not_index; } const wchar_t *pwd; return_pwd: pwd = getvar(L VAR_PWD); if (pwd == NULL) { if (printerror) xerror(0, Ngt("$PWD is not set")); return false; } *entryp = pwd; *indexp = (size_t) num; return true; not_index: *entryp = indexstr; *indexp = SIZE_MAX; return true; out_of_range: if (printerror) xerror(0, Ngt("index %ls is out of range"), indexstr); return false; } #endif /* YASH_ENABLE_DIRSTACK */ #if YASH_ENABLE_LINEEDIT /* Generates candidates to complete a directory stack index. */ /* The prototype of this function is declared in "lineedit/complete.h". */ void generate_dirstack_candidates(const le_compopt_T *compopt) { if (!(compopt->type & CGT_DIRSTACK)) return; le_compdebug("adding directory stack index candidates"); #if YASH_ENABLE_DIRSTACK if (!le_compile_cpatterns(compopt)) return; variable_T *dirstack = search_variable(L VAR_DIRSTACK); size_t totalcount = 1; wchar_t *index; if (dirstack != NULL && (dirstack->v_type & VF_MASK) == VF_ARRAY) { size_t count = dirstack->v_valc; for (size_t i = 0; i < count; i++) { switch (compopt->src[0]) { case L'+': index = malloc_wprintf(L"+%zu", count - i); break; case L'-': index = malloc_wprintf(L"-%zu", i); break; default: return; } if (le_wmatch_comppatterns(compopt, index)) le_new_candidate( CT_WORD, index, xwcsdup(dirstack->v_vals[i]), compopt); else free(index); } totalcount += count; } switch (compopt->src[0]) { case L'+': index = malloc_wprintf(L"+%zu", 0); break; case L'-': index = malloc_wprintf(L"-%zu", totalcount - 1); break; default: return; } if (le_wmatch_comppatterns(compopt, index)) { const wchar_t *pwd = getvar(L VAR_PWD); wchar_t *duppwd = (pwd != NULL) ? xwcsdup(pwd) : NULL; le_new_candidate(CT_WORD, index, duppwd, compopt); } else { free(index); } #else /* YASH_ENABLE_DIRSTACK */ le_compdebug(" directory stack is disabled"); #endif } #endif /* YASH_ENABLE_LINEEDIT */ /********** Built-ins **********/ struct reading_option_T; static void print_variable( const wchar_t *name, const variable_T *var, const wchar_t *argv0, bool readonly, bool export) __attribute__((nonnull)); static void print_scalar(const wchar_t *name, bool namequote, const variable_T *var, const wchar_t *argv0) __attribute__((nonnull)); static void print_array( const wchar_t *name, const variable_T *var, const wchar_t *argv0) __attribute__((nonnull)); static void print_function( const wchar_t *name, const function_T *func, const wchar_t *argv0, bool readonly) __attribute__((nonnull)); #if YASH_ENABLE_ARRAY static int array_dump_all(const wchar_t *argv0); static void array_remove_elements( variable_T *array, size_t count, void *const *indexwcss) __attribute__((nonnull)); static int compare_long(const void *lp1, const void *lp2) __attribute__((nonnull,pure)); static void array_insert_elements( variable_T *array, size_t count, void *const *values) __attribute__((nonnull)); static void array_set_element(const wchar_t *name, variable_T *array, const wchar_t *indexword, const wchar_t *value) __attribute__((nonnull)); #endif /* YASH_ENABLE_ARRAY */ static bool unset_function(const wchar_t *name) __attribute__((nonnull)); static bool unset_variable(const wchar_t *name) __attribute__((nonnull)); static bool check_options(const wchar_t *options) __attribute__((nonnull,pure)); static bool set_optind(unsigned long optind, unsigned long optsubind); static inline bool set_optarg(const wchar_t *value); static bool set_variable_single_char(const wchar_t *varname, wchar_t value) __attribute__((nonnull)); static bool read_with_prompt(xwcsbuf_T *buf, xstrbuf_T *split, const struct reading_option_T *ro) __attribute__((nonnull)); static struct promptset_T promptset_for_read( bool firstline, const struct reading_option_T *ro) __attribute__((nonnull,warn_unused_result)); static wchar_t *read_one_line_with_prompt( struct promptset_T prompt, bool lineedit) __attribute__((malloc,warn_unused_result)); static wchar_t *read_one_line(void) __attribute__((malloc,warn_unused_result)); static bool unescape_line(const wchar_t *line, xwcsbuf_T *buf, xstrbuf_T *split) __attribute__((nonnull)); static void assign_array(const wchar_t *name, const plist_T *ranges, size_t i) __attribute__((nonnull)); /* Options for the "typeset" built-in. */ const struct xgetopt_T typeset_options[] = { { L'f', L"functions", OPTARG_NONE, false, NULL, }, { L'g', L"global", OPTARG_NONE, false, NULL, }, { L'p', L"print", OPTARG_NONE, true, NULL, }, { L'r', L"readonly", OPTARG_NONE, false, NULL, }, { L'x', L"export", OPTARG_NONE, false, NULL, }, { L'X', L"unexport", OPTARG_NONE, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* Note: `local_options' is defined as part of `typeset_options'. */ /* The "typeset" built-in, which accepts the following options: * -f: affect functions rather than variables * -g: global * -p: print variables * -r: make variables readonly * -x: export variables * -X: cancel exportation of variables * Equivalent built-ins: * export: typeset -gx * local: typeset * readonly: typeset -gr * The "set" built-in without any arguments is redirected to this built-in. */ int typeset_builtin(int argc, void **argv) { bool function = false, global = false, print = false; bool readonly = false, export = false, unexport = false; const struct xgetopt_T *options = (ARGV(0)[0] == L'l' /*local*/) ? local_options : typeset_options; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, options, 0)) != NULL) { switch (opt->shortopt) { case L'f': function = true; break; case L'g': global = true; break; case L'p': print = true; break; case L'r': readonly = true; break; case L'x': export = true; break; case L'X': unexport = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } switch (ARGV(0)[0]) { case L'e': assert(wcscmp(ARGV(0), L"export") == 0); global = true; if (!unexport) export = true; break; case L'l': assert(wcscmp(ARGV(0), L"local") == 0); break; case L'r': assert(wcscmp(ARGV(0), L"readonly") == 0); global = readonly = true; break; case L's': assert(wcscmp(ARGV(0), L"set") == 0); global = true; break; case L't': assert(wcscmp(ARGV(0), L"typeset") == 0); break; default: assert(false); } if (function && global && ARGV(0)[0] == L't' /*typeset*/) return special_builtin_error( mutually_exclusive_option_error(L'f', L'g')); if (function && export) return special_builtin_error( mutually_exclusive_option_error(L'f', L'x')); if (function && unexport) return special_builtin_error( mutually_exclusive_option_error(L'f', L'X')); if (export && unexport) return special_builtin_error( mutually_exclusive_option_error(L'x', L'X')); if (xoptind == argc) { kvpair_T *kvs; size_t count; if (!function) { /* print all variables */ count = make_array_of_all_variables(global, &kvs); qsort(kvs, count, sizeof *kvs, keywcscoll); for (size_t i = 0; yash_error_message_count == 0 && i < count; i++) print_variable( kvs[i].key, kvs[i].value, ARGV(0), readonly, export); } else { /* print all functions */ kvs = ht_tokvarray(&functions); count = functions.count; qsort(kvs, count, sizeof *kvs, keywcscoll); for (size_t i = 0; yash_error_message_count == 0 && i < count; i++) print_function(kvs[i].key, kvs[i].value, ARGV(0), readonly); } free(kvs); } else { do { wchar_t *arg = ARGV(xoptind); if (!function) { wchar_t *wequal = wcschr(arg, L'='); if (wequal != NULL) *wequal = L'\0'; if (wequal != NULL || !print) { /* create/assign variable */ variable_T *var = global ? new_global(arg) : new_local(arg); vartype_T saveexport = var->v_type & VF_EXPORT; if (wequal != NULL) { if (var->v_type & VF_READONLY) { xerror(0, Ngt("$%ls is read-only"), arg); } else { varvaluefree(var); var->v_type = VF_SCALAR | (var->v_type & ~VF_MASK); var->v_value = xwcsdup(&wequal[1]); var->v_getter = NULL; } } if (readonly) var->v_type |= VF_READONLY | VF_NODELETE; if (export) var->v_type |= VF_EXPORT; else if (unexport) var->v_type &= ~VF_EXPORT; variable_set(arg, var); if (saveexport != (var->v_type & VF_EXPORT) || (wequal != NULL && (var->v_type & VF_EXPORT))) update_environment(arg); } else { /* print the variable */ variable_T *var = search_variable(arg); if (var != NULL) { print_variable(arg, var, ARGV(0), readonly, export); } else { xerror(0, Ngt("no such variable $%ls"), arg); } } } else { /* treat function */ function_T *f = ht_get(&functions, arg).value; if (f != NULL) { if (print) { if (!readonly || (f->f_type & VF_READONLY)) print_function(arg, f, ARGV(0), readonly); } else { if (readonly) f->f_type |= VF_READONLY | VF_NODELETE; } } else { xerror(0, Ngt("no such function `%ls'"), arg); } } } while (++xoptind < argc); } return (yash_error_message_count == 0) ? Exit_SUCCESS : special_builtin_error(Exit_FAILURE); } /* Prints the specified variable to the standard output. * This function does not print special variables whose name begins with an '='. * If `readonly' or `export' is true, the variable is printed only if it is * read-only or exported, respectively. The `name' is quoted if `is_name(name)' * is not true. * An error message is printed to the standard error on error. */ void print_variable( const wchar_t *name, const variable_T *var, const wchar_t *argv0, bool readonly, bool export) { wchar_t *qname = NULL; if (name[0] == L'=') return; if (readonly && !(var->v_type & VF_READONLY)) return; if (export && !(var->v_type & VF_EXPORT)) return; if (!is_name(name)) name = qname = quote_as_word(name); switch (var->v_type & VF_MASK) { case VF_SCALAR: print_scalar(name, qname != NULL, var, argv0); break; case VF_ARRAY: print_array(name, var, argv0); break; } free(qname); } /* Prints the specified scalar variable to the standard output. * `namequote' must equal `is_name(name)'. If `argv0' is "set" and `namequote' * is true, the variable is not printed since it cannot be assigned in the * normal assignment syntax. * An error message is printed to the standard error on error. */ void print_scalar(const wchar_t *name, bool namequote, const variable_T *var, const wchar_t *argv0) { wchar_t *quotedvalue; const char *format; xstrbuf_T opts; if (var->v_value != NULL) quotedvalue = quote_as_word(var->v_value); else quotedvalue = NULL; switch (argv0[0]) { case L's': assert(wcscmp(argv0, L"set") == 0); if (!namequote && quotedvalue != NULL) xprintf("%ls=%ls\n", name, quotedvalue); break; case L'e': case L'r': assert(wcscmp(argv0, L"export") == 0 || wcscmp(argv0, L"readonly") == 0); format = (quotedvalue != NULL) ? "%ls %ls=%ls\n" : "%ls %ls\n"; xprintf(format, argv0, name, quotedvalue); break; case L'l': assert(wcscmp(argv0, L"local") == 0); goto typeset; case L't': assert(wcscmp(argv0, L"typeset") == 0); typeset: sb_init(&opts); if (var->v_type & VF_EXPORT) sb_ccat(&opts, 'x'); if (var->v_type & VF_READONLY) sb_ccat(&opts, 'r'); if (opts.length > 0) sb_insert(&opts, 0, " -"); format = (quotedvalue != NULL) ? "%ls%s %ls=%ls\n" : "%ls%s %ls\n"; xprintf(format, argv0, opts.contents, name, quotedvalue); sb_destroy(&opts); break; default: assert(false); } free(quotedvalue); } /* Prints the specified array variable to the standard output. * An error message is printed to the standard error on error. */ void print_array( const wchar_t *name, const variable_T *var, const wchar_t *argv0) { xstrbuf_T opts; if (!xprintf("%ls=(", name)) return; if (var->v_valc > 0) { for (size_t i = 0; ; ) { wchar_t *qvalue = quote_as_word(var->v_vals[i]); bool ok = xprintf("%ls", qvalue); free(qvalue); if (!ok) return; i++; if (i >= var->v_valc) break; if (!xprintf(" ")) return; } } if (!xprintf(")\n")) return; switch (argv0[0]) { case L'a': assert(wcscmp(argv0, L"array") == 0); break; case L's': assert(wcscmp(argv0, L"set") == 0); break; case L'e': case L'r': assert(wcscmp(argv0, L"export") == 0 || wcscmp(argv0, L"readonly") == 0); xprintf("%ls %ls\n", argv0, name); break; case L'l': assert(wcscmp(argv0, L"local") == 0); goto typeset; case L't': assert(wcscmp(argv0, L"typeset") == 0); typeset: sb_init(&opts); if (var->v_type & VF_EXPORT) sb_ccat(&opts, 'x'); if (var->v_type & VF_READONLY) sb_ccat(&opts, 'r'); if (opts.length > 0) sb_insert(&opts, 0, " -"); xprintf("%ls%s %ls\n", argv0, opts.contents, name); sb_destroy(&opts); break; default: assert(false); } } /* Prints the specified function to the standard output. * If `readonly' is true, the function is printed only if it is read-only. * An error message is printed to the standard error if failed to print to the * standard output. */ void print_function( const wchar_t *name, const function_T *func, const wchar_t *argv0, bool readonly) { if (readonly && !(func->f_type & VF_READONLY)) return; wchar_t *qname = NULL; if (!is_name(name)) name = qname = quote_as_word(name); wchar_t *value = command_to_wcs(func->f_body, true); const char *format = (qname == NULL) ? "%ls()\n%ls" : "function %ls()\n%ls"; bool ok = xprintf(format, name, value); free(value); if (!ok) goto end; switch (argv0[0]) { case L'r': assert(wcscmp(argv0, L"readonly") == 0); if (func->f_type & VF_READONLY) xprintf("%ls -f %ls\n", argv0, name); break; case L't': assert(wcscmp(argv0, L"typeset") == 0); if (func->f_type & VF_READONLY) xprintf("%ls -fr %ls\n", argv0, name); break; default: assert(false); } end: free(qname); } #if YASH_ENABLE_HELP const char typeset_help[] = Ngt( "set or print variables" ); const char typeset_syntax[] = Ngt( "\ttypeset [-fgprxX] [name[=value]...]\n" ); const char export_help[] = Ngt( "export variables as environment variables" ); const char export_syntax[] = Ngt( "\texport [-prX] [name[=value]...]\n" ); const char local_help[] = Ngt( "set or print local variables" ); const char local_syntax[] = Ngt( "\tlocal [-prxX] [name[=value]...]\n" ); const char readonly_help[] = Ngt( "make variables read-only" ); const char readonly_syntax[] = Ngt( "\treadonly [-fpxX] [name[=value]...]\n" ); #endif #if YASH_ENABLE_ARRAY /* Options for the "array" built-in. */ const struct xgetopt_T array_options[] = { { L'd', L"delete", OPTARG_NONE, true, NULL, }, { L'i', L"insert", OPTARG_NONE, true, NULL, }, { L's', L"set", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "array" built-in, which accepts the following options: * -d: delete an array element * -i: insert an array element * -s: set an array element value */ int array_builtin(int argc, void **argv) { enum { NONE = 0, DELETE = 1 << 0, INSERT = 1 << 1, SET = 1 << 2, } options = NONE; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, array_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'd': options |= DELETE; break; case L'i': options |= INSERT; break; case L's': options |= SET; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } /* error checks */ if (options != NONE && (options & (options - 1)) != 0) { xerror(0, Ngt("more than one option cannot be used at once")); return Exit_ERROR; } size_t min, max; switch (options) { case NONE: min = 0; max = SIZE_MAX; break; case DELETE: min = 1; max = SIZE_MAX; break; case INSERT: min = 2; max = SIZE_MAX; break; case SET: min = 3; max = 3; break; default: assert(false); } if (!validate_operand_count(argc - xoptind, min, max)) return Exit_ERROR; if (xoptind == argc) return array_dump_all(ARGV(0)); const wchar_t *name = ARGV(xoptind++); if (wcschr(name, L'=') != NULL) { xerror(0, Ngt("`%ls' is not a valid array name"), name); return Exit_FAILURE; } if (options == 0) { set_array(name, argc - xoptind, pldup(&argv[xoptind], copyaswcs), SCOPE_GLOBAL, false); } else { variable_T *array = search_array_and_check_if_changeable(name); if (array == NULL) return Exit_FAILURE; switch (options) { case DELETE: array_remove_elements(array, argc - xoptind, &argv[xoptind]); break; case INSERT: array_insert_elements(array, argc - xoptind, &argv[xoptind]); break; case SET: array_set_element( name, array, ARGV(xoptind), ARGV(xoptind + 1)); break; default: assert(false); } } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if LONG_MAX < SIZE_MAX # define LONG_LT_SIZE(longvalue,sizevalue) \ ((size_t) (longvalue) < (sizevalue)) #else # define LONG_LT_SIZE(longvalue,sizevalue) \ ((longvalue) < (long) (sizevalue)) #endif /* Prints all existing arrays. * Returns an exit status to be returned by the array built-in. */ int array_dump_all(const wchar_t *argv0) { kvpair_T *kvs; size_t count = make_array_of_all_variables(true, &kvs); qsort(kvs, count, sizeof *kvs, keywcscoll); for (size_t i = 0; yash_error_message_count == 0 && i < count; i++) { variable_T *var = kvs[i].value; if ((var->v_type & VF_MASK) == VF_ARRAY) print_variable(kvs[i].key, var, argv0, false, false); } free(kvs); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Removes elements from `array'. * `indexwcss' is an NULL-terminated array of pointers to wide strings, * which are parsed as indices of elements to be removed. * `count' is the number of elements in `indexwcss'. * An error message is printed to the standard error on error. */ void array_remove_elements( variable_T *array, size_t count, void *const *indexwcss) { long indices[count]; assert((array->v_type & VF_MASK) == VF_ARRAY); /* convert all the strings into long integers */ for (size_t i = 0; i < count; i++) { const wchar_t *indexwcs = indexwcss[i]; if (!xwcstol(indexwcs, 10, &indices[i])) { xerror(errno, Ngt("`%ls' is not a valid integer"), indexwcs); return; } if (indices[i] >= 0) { indices[i] -= 1; } else { #if LONG_MAX < SIZE_MAX if (!LONG_LT_SIZE(LONG_MAX, array->v_valc)) #endif indices[i] += array->v_valc; } } /* sort all the indices. */ qsort(indices, count, sizeof *indices, compare_long); /* remove elements in descending order so that an earlier removal does not * affect the indices for later removals. */ plist_T list; long lastindex = LONG_MIN; pl_initwith(&list, array->v_vals, array->v_valc); for (size_t i = count; i-- != 0; ) { long index = indices[i]; if (index == lastindex) continue; if (0 <= index && LONG_LT_SIZE(index, list.length)) { free(list.contents[index]); pl_remove(&list, index, 1); } lastindex = index; } array->v_valc = list.length; array->v_vals = pl_toary(&list); } int compare_long(const void *lp1, const void *lp2) { long l1 = *(const long *) lp1, l2 = *(const long *) lp2; return l1 == l2 ? 0 : l1 < l2 ? -1 : 1; } /* Inserts the specified elements into the specified array. * `values' is an NULL-terminated array of pointers to wide strings. * The first string in `values' is parsed as the integer index that specifies * where to insert the elements. The other strings are inserted to the array. * `count' is the number of strings in `values' including the first index * string. * An error message is printed to the standard error on error. */ void array_insert_elements( variable_T *array, size_t count, void *const *values) { long index; assert((array->v_type & VF_MASK) == VF_ARRAY); assert(count > 0); assert(values[0] != NULL); { const wchar_t *indexword = *values; if (!xwcstol(indexword, 10, &index)) { xerror(errno, Ngt("`%ls' is not a valid integer"), indexword); return; } } count--, values++; assert(plcount(values) == count); if (index < 0) { index += array->v_valc + 1; if (index < 0) index = 0; } size_t uindex; if (LONG_LT_SIZE(index, array->v_valc)) uindex = (size_t) index; else uindex = array->v_valc; plist_T list; pl_initwith(&list, array->v_vals, array->v_valc); pl_insert(&list, uindex, values); for (size_t i = 0; i < count; i++) list.contents[uindex + i] = xwcsdup(list.contents[uindex + i]); array->v_valc = list.length; array->v_vals = pl_toary(&list); } /* Sets the value of the specified element of the array. * `name' is the name of the array variable. * `indexword' is parsed as the integer index of the element. * An error message is printed to the standard error on error. */ void array_set_element(const wchar_t *name, variable_T *array, const wchar_t *indexword, const wchar_t *value) { assert((array->v_type & VF_MASK) == VF_ARRAY); long index; if (!xwcstol(indexword, 10, &index)) { xerror(errno, Ngt("`%ls' is not a valid integer"), indexword); return; } size_t uindex; if (index < 0) { index += array->v_valc; if (index < 0) goto invalid_index; assert(LONG_LT_SIZE(index, array->v_valc)); uindex = (size_t) index; } else if (index > 0) { if (!LONG_LT_SIZE(index - 1, array->v_valc)) goto invalid_index; uindex = (size_t) index - 1; } else { goto invalid_index; } assert(uindex < array->v_valc); free(array->v_vals[uindex]); array->v_vals[uindex] = xwcsdup(value); return; invalid_index: xerror(0, Ngt("index %ls is out of range " "(the actual size of array $%ls is %zu)"), indexword, name, array->v_valc); } #if YASH_ENABLE_HELP const char array_help[] = Ngt( "manipulate an array" ); const char array_syntax[] = Ngt( "\tarray # print arrays\n" "\tarray name [value...] # set array values\n" "\tarray -d name [index...]\n" "\tarray -i name index [value...]\n" "\tarray -s name index value\n" ); #endif #endif /* YASH_ENABLE_ARRAY */ /* Options for the "unset" built-in. */ const struct xgetopt_T unset_options[] = { { L'f', L"functions", OPTARG_NONE, true, NULL, }, { L'v', L"variables", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "unset" built-in, which accepts the following options: * -f: deletes functions * -v: deletes variables (default) */ int unset_builtin(int argc, void **argv) { bool function = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, unset_options, 0)) != NULL) { switch (opt->shortopt) { case L'f': function = true; break; case L'v': function = false; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (posixly_correct && xoptind == argc) return insufficient_operands_error(1); for (; xoptind < argc; xoptind++) { const wchar_t *name = ARGV(xoptind); if (function) { unset_function(name); } else { if (wcschr(name, L'=')) continue; unset_variable(name); } } return (yash_error_message_count == 0) ? Exit_SUCCESS : special_builtin_error(Exit_FAILURE); } /* Unsets the specified function. * On error, an error message is printed to the standard error and TRUE is * returned. */ bool unset_function(const wchar_t *name) { kvpair_T kv = ht_remove(&functions, name); function_T *f = kv.value; if (f != NULL) { if (!(f->f_type & VF_NODELETE)) { funckvfree(kv); } else { xerror(0, Ngt("function `%ls' is read-only"), name); ht_set(&functions, kv.key, kv.value); return true; } } return false; } /* Unsets the specified variable. * On error, an error message is printed to the standard error and TRUE is * returned. */ bool unset_variable(const wchar_t *name) { for (environ_T *env = current_env; env != NULL; env = env->parent) { kvpair_T kv = ht_remove(&env->contents, name); variable_T *var = kv.value; if (var != NULL) { if (!(var->v_type & VF_NODELETE)) { bool exported = var->v_type & VF_EXPORT; varkvfree(kv); variable_set(name, NULL); if (exported) update_environment(name); return false; } else { xerror(0, Ngt("$%ls is read-only"), name); ht_set(&env->contents, kv.key, kv.value); return true; } } } return false; } #if YASH_ENABLE_HELP const char unset_help[] = Ngt( "remove variables or functions" ); const char unset_syntax[] = Ngt( "\tunset [-fv] [name...]\n" ); #endif /* Options for the "shift" built-in. */ const struct xgetopt_T shift_options[] = { { L'A', L"array", OPTARG_REQUIRED, false, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "shift" built-in */ int shift_builtin(int argc, void **argv) { const wchar_t *arrayname = NULL; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, shift_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'A': arrayname = xoptarg; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (!validate_operand_count(argc - xoptind, 0, 1)) return special_builtin_error(Exit_ERROR); long count; if (xoptind < argc) { if (!xwcstol(ARGV(xoptind), 10, &count)) { xerror(errno, Ngt("`%ls' is not a valid integer"), ARGV(xoptind)); return special_builtin_error(Exit_ERROR); } else if (posixly_correct && count < 0) { xerror(0, Ngt("%ls: the operand value must not be negative"), ARGV(xoptind)); return special_builtin_error(Exit_ERROR); } } else { count = 1; } variable_T *var; if (arrayname == NULL) { var = search_variable(L VAR_positional); assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); } else { var = search_variable(arrayname); if (wcschr(arrayname, L'=') != NULL || var == NULL || (var->v_type & VF_MASK) != VF_ARRAY) { xerror(0, Ngt("$%ls is not an array"), arrayname); return Exit_FAILURE; } } unsigned long abscount = (count >= 0) ? (unsigned long) count : -(unsigned long) count; if ( #if ULONG_MAX > SIZE_MAX abscount > (unsigned long) var->v_valc #else (size_t) abscount > var->v_valc #endif ) { const char *message; if (arrayname == NULL) { message = ngt("%ld: cannot shift so many " "(there is only one positional parameter)", "%ld: cannot shift so many " "(there are only %zu positional parameters)", var->v_valc); } else { message = ngt("%ld: cannot shift so many " "(there is only one array element)", "%ld: cannot shift so many " "(there are only %zu array elements)", var->v_valc); } xerror(0, message, count, var->v_valc); return Exit_FAILURE; } size_t from = (count >= 0) ? 0 : (var->v_valc - (size_t) abscount); plist_T list; pl_initwith(&list, var->v_vals, var->v_valc); for (size_t i = 0; i < (size_t) abscount; i++) free(list.contents[from + i]); pl_remove(&list, from, (size_t) abscount); var->v_valc = list.length; var->v_vals = pl_toary(&list); return Exit_SUCCESS; } #if YASH_ENABLE_HELP const char shift_help[] = Ngt( "remove some positional parameters or array elements" ); const char shift_syntax[] = Ngt( "\tshift [-A array_name] [count]\n" ); #endif /* The "getopts" built-in */ int getopts_builtin(int argc, void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, XGETOPT_POSIX)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (!validate_operand_count(argc - xoptind, 2, SIZE_MAX)) return Exit_ERROR; const wchar_t *options = ARGV(xoptind++); const wchar_t *varname = ARGV(xoptind++); void *const *args; unsigned long optind, optsubind; const wchar_t *arg, *optp; wchar_t optchar; if (wcschr(varname, L'=')) { xerror(0, Ngt("`%ls' is not a valid variable name"), varname); return Exit_FAILURE; } else if (!check_options(options)) { xerror(0, Ngt("`%ls' is not a valid option specification"), options); return Exit_FAILURE; } /* Parse $OPTIND */ { const wchar_t *varoptind = getvar(L VAR_OPTIND); wchar_t *endp; if (varoptind == NULL || varoptind[0] == L'\0') goto optind_invalid; errno = 0; optind = wcstoul(varoptind, &endp, 10); if (errno != 0 || varoptind == endp) goto optind_invalid; optind -= 1; if (*endp == L':') { endp++; if (!xwcstoul(endp, 10, &optsubind) || optsubind == 0) goto optind_invalid; } else { optsubind = 1; } } if (xoptind < argc) { if (optind >= (unsigned long) (argc - xoptind)) goto no_more_options; args = &argv[xoptind]; } else { variable_T *var = search_variable(L VAR_positional); assert(var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); if (optind >= var->v_valc) goto no_more_options; args = var->v_vals; } #define TRY(exp) do { if (!(exp)) return Exit_FAILURE; } while (0) parse_arg: arg = args[optind]; if (arg == NULL || arg[0] != L'-' || arg[1] == L'\0') { goto no_more_options; } else if (arg[1] == L'-' && arg[2] == L'\0') { /* arg == "--" */ optind++; goto no_more_options; } else if (xwcsnlen(arg, optsubind + 1) <= optsubind) { optind++, optsubind = 1; goto parse_arg; } optchar = arg[optsubind++]; assert(optchar != L'\0'); if (optchar == L':' || (optp = wcschr(options, optchar)) == NULL) { /* invalid option */ TRY(set_variable_single_char(varname, L'?')); if (options[0] == L':') { TRY(set_variable_single_char(L VAR_OPTARG, optchar)); } else { fprintf(stderr, gt("%ls: `-%lc' is not a valid option\n"), command_name, (wint_t) optchar); TRY(!unset_variable(L VAR_OPTARG)); } } else { /* valid option */ if (optp[1] != L':') { /* option without an argument */ TRY(!unset_variable(L VAR_OPTARG)); } else { /* option with an argument */ const wchar_t *optarg = &arg[optsubind]; optsubind = 1; optind++; if (optarg[0] == L'\0') { optarg = args[optind++]; if (optarg == NULL) { /* argument is missing */ if (options[0] == L':') { TRY(set_variable_single_char(varname, L':')); TRY(set_variable_single_char(L VAR_OPTARG, optchar)); } else { fprintf(stderr, gt("%ls: the -%lc option's argument is missing\n"), command_name, (wint_t) optchar); TRY(set_variable_single_char(varname, L'?')); TRY(!unset_variable(L VAR_OPTARG)); } goto finish; } } TRY(set_optarg(optarg)); } TRY(set_variable_single_char(varname, optchar)); } finish: TRY(set_optind(optind, optsubind)); return Exit_SUCCESS; #undef TRY no_more_options: set_optind(optind, 0); set_variable_single_char(varname, L'?'); unset_variable(L VAR_OPTARG); return Exit_FAILURE; optind_invalid: xerror(0, Ngt("$OPTIND has an invalid value")); return Exit_FAILURE; } /* Checks if the `options' is valid. Returns true iff OK. */ bool check_options(const wchar_t *options) { if (options[0] == L':') options++; for (;;) switch (*options) { case L'\0': return true; case L':': return false; case L'?': if (posixly_correct) return false; /* falls thru! */ default: if (posixly_correct && !iswalnum(*options)) return false; options++; if (*options == L':') options++; continue; } } /* Sets $OPTIND to `optind' plus 1, followed by `optsubind' (if > 1). */ bool set_optind(unsigned long optind, unsigned long optsubind) { wchar_t *value = malloc_wprintf( optsubind > 1 ? L"%lu:%lu" : L"%lu", optind + 1, optsubind); return set_variable(L VAR_OPTIND, value, SCOPE_GLOBAL, shopt_allexport); } /* Sets $OPTARG to `value'. */ bool set_optarg(const wchar_t *value) { return set_variable(L VAR_OPTARG, xwcsdup(value), SCOPE_GLOBAL, shopt_allexport); } /* Sets the specified variable to the single character `value'. */ bool set_variable_single_char(const wchar_t *varname, wchar_t value) { wchar_t *v = xmallocn(2, sizeof *v); v[0] = value; v[1] = L'\0'; return set_variable(varname, v, SCOPE_GLOBAL, shopt_allexport); } #if YASH_ENABLE_HELP const char getopts_help[] = Ngt( "parse command options" ); const char getopts_syntax[] = Ngt( "\tgetopts options variable [argument...]\n" ); #endif /* Options for the "read" built-in. */ const struct xgetopt_T read_options[] = { { L'A', L"array", OPTARG_NONE, false, NULL, }, { L'e', L"line-editing", OPTARG_NONE, false, NULL, }, { L'P', L"ps1", OPTARG_NONE, false, NULL, }, { L'p', L"prompt", OPTARG_REQUIRED, false, NULL, }, { L'r', L"raw-mode", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; struct reading_option_T { bool array, lineedit, ps1, raw; const wchar_t *prompt; }; /* The "read" built-in, which accepts the following options: * -A: assign values to array * -e: use line-editing * -P: use $PS1 * -p: specify prompt * -r: don't treat backslashes specially */ int read_builtin(int argc, void **argv) { struct reading_option_T ro = { .array = false, .lineedit = false, .ps1 = false, .raw = false, .prompt = NULL, }; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, read_options, 0)) != NULL) { switch (opt->shortopt) { case L'A': ro.array = true; break; case L'e': ro.lineedit = true; break; case L'P': ro.ps1 = true; break; case L'p': ro.prompt = xoptarg; break; case L'r': ro.raw = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (ro.ps1 && ro.prompt != NULL) return mutually_exclusive_option_error(L'P', L'p'); if (xoptind == argc) return insufficient_operands_error(1); /* check if the identifiers are valid */ for (int i = xoptind; i < argc; i++) { if (wcschr(ARGV(i), L'=') != NULL) { xerror(0, Ngt("`%ls' is not a valid variable name"), ARGV(i)); return Exit_FAILURE; } } xwcsbuf_T buf; xstrbuf_T split; wb_init(&buf); sb_init(&split); if (!read_with_prompt(&buf, &split, &ro)) { sb_destroy(&split); wb_destroy(&buf); return Exit_FAILURE; } /* remove trailing newline */ bool eof; if (buf.length > 0 && buf.contents[buf.length - 1] == L'\n') { wb_truncate(&buf, buf.length - 1); eof = false; } else { /* no newline means the EOF was encountered */ eof = true; } /* split fields */ const wchar_t *tail; plist_T list; pl_init(&list); { const wchar_t *ifs = getvar(L VAR_IFS); if (ifs == NULL) ifs = DEFAULT_IFS; tail = extract_fields(buf.contents, split.contents, false, ifs, &list); assert(list.length % 2 == 0); } /* Add missing empty fields */ size_t count = (size_t) argc - xoptind; for (size_t i = list.length / 2; i < count; i++) pl_add(pl_add(&list, buf.contents), buf.contents); assert(list.length % 2 == 0); assert(list.length > 0); /* assign variables except last */ const wchar_t *name; for (size_t i = 0; i < count - 1; i++) { const wchar_t *start = list.contents[2 * i]; const wchar_t *end = list.contents[2 * i + 1]; wchar_t *field = xwcsndup(start, end - start); name = ARGV(xoptind + i); set_variable(name, field, SCOPE_GLOBAL, shopt_allexport); } /* assign last variable */ name = ARGV(xoptind + count - 1); if (ro.array) { assign_array(name, &list, 2 * (count - 1)); } else { size_t i = count - 1; const wchar_t *start = list.contents[2 * i]; const wchar_t *end = list.contents[2 * i + 1]; if (2 * count < list.length) end = tail; wchar_t *field = xwcsndup(start, end - start); set_variable(name, field, SCOPE_GLOBAL, shopt_allexport); } pl_destroy(&list); sb_destroy(&split); wb_destroy(&buf); return (!eof && yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } /* Reads one line from the standard input. The result is appended to `buf' and * `split'. `buf' will contain no escapes or other special characters. `split' * is the splittability string for `buf'. The string is splittable at characters * that were not backslash-escaped. * If `ro->raw' is true, exactly one line is read and backslashes are not * treated as escapes. Otherwise, line continuations cause this function to read * more and backslash escapes are recognized. * Returns false on error while reading. */ bool read_with_prompt(xwcsbuf_T *buf, xstrbuf_T *split, const struct reading_option_T *ro) { bool firstline = true; bool completed = false; bool use_prompt = is_interactive_now && isatty(STDIN_FILENO); while (!completed) { wchar_t *line; if (use_prompt) { struct promptset_T prompt = promptset_for_read(firstline, ro); line = read_one_line_with_prompt(prompt, ro->lineedit); free_prompt(prompt); } else { line = read_one_line(); } if (line == NULL) return false; if (ro->raw) { wb_cat(buf, line); sb_ccat_repeat(split, true, wcslen(line)); completed = true; } else { completed = unescape_line(line, buf, split); } free(line); firstline = false; } return true; } /* Returns a prompt set for reading in the "read" built-in. */ struct promptset_T promptset_for_read( bool firstline, const struct reading_option_T *ro) { if (!firstline) return get_prompt(2); if (ro->ps1) return get_prompt(1); struct promptset_T ps; ps.main = escape(ro->prompt != NULL ? ro->prompt : L"", L"\\"); ps.right = xwcsdup(L""); ps.styler = xwcsdup(L""); return ps; } /* Reads one line from the standard input with the specified prompt. * If `lineedit' is true, use line-editing if possible. * The result is returned as a newly-malloced wide string. The result is null * iff an error occurs. */ wchar_t *read_one_line_with_prompt(struct promptset_T prompt, bool lineedit) { wchar_t *line; if (lineedit) { #if YASH_ENABLE_LINEEDIT if (shopt_lineedit != SHOPT_NOLINEEDIT) { switch (le_readline(prompt, false, &line)) { case INPUT_OK: return line; case INPUT_EOF: return xwcsdup(L""); case INPUT_INTERRUPTED: set_interrupted(); return NULL; case INPUT_ERROR: break; } } #endif // YASH_ENABLE_LINEEDIT } print_prompt(prompt.main); print_prompt(prompt.styler); line = read_one_line(); print_prompt(PROMPT_RESET); return line; } /* Reads one line from the standard input without printing any prompt or using * line-editing. * The result is returned as a newly-malloced wide string. The result is null * iff an error occurs. */ wchar_t *read_one_line(void) { xwcsbuf_T buf; wb_init(&buf); if (read_input(&buf, stdin_input_file_info, false) != INPUT_ERROR) return wb_towcs(&buf); wb_destroy(&buf); return NULL; } /* Parses a string that may contain backslash escapes. * Unescaped `line' is appended to `buf' with a corresponding splittability * string appended to `split'. Characters are splittable iff not escaped. * The result is false iff `line' ends with a line continuation. * The line continuation is not appended to `buf'. */ bool unescape_line(const wchar_t *line, xwcsbuf_T *buf, xstrbuf_T *split) { for (;;) { bool splitchar; switch (*line) { case L'\0': return true; case L'\\': line++; switch (*line) { case L'\0': return true; case L'\n': return false; } splitchar = false; break; default: splitchar = true; break; } wb_wccat(buf, *line); sb_ccat(split, splitchar); line++; } } /* Assigns a result of field-splitting contained in `ranges' to an array named * `name'. Fields are assigned starting from index `i' in `ranges'. */ void assign_array(const wchar_t *name, const plist_T *ranges, size_t i) { assert((ranges->length - i) % 2 == 0); plist_T fields; pl_init(&fields); while (i < ranges->length) { const wchar_t *start = ranges->contents[i++]; const wchar_t *end = ranges->contents[i++]; wchar_t *field = xwcsndup(start, end - start); pl_add(&fields, field); } if (fields.length == 1 && ((wchar_t *) fields.contents[0])[0] == L'\0') { free(fields.contents[0]); pl_remove(&fields, 0, 1); } set_array(name, fields.length, pl_toary(&fields), SCOPE_GLOBAL, shopt_allexport); } #if YASH_ENABLE_HELP const char read_help[] = Ngt( "read a line from the standard input" ); const char read_syntax[] = Ngt( "\tread [-Aer] [-P|-p] variable...\n" ); #endif /* options for the "pushd" built-in */ const struct xgetopt_T pushd_options[] = { #if YASH_ENABLE_DIRSTACK { L'D', L"remove-duplicates", OPTARG_NONE, true, NULL, }, #endif { L'd', L"default-directory", OPTARG_REQUIRED, false, NULL, }, { L'L', L"logical", OPTARG_NONE, true, NULL, }, { L'P', L"physical", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* Note: `cd_options' and `pwd_options' are defined as part of `pushd_options'. */ #if YASH_ENABLE_DIRSTACK static variable_T *get_dirstack(void); static void push_dirstack(variable_T *var, wchar_t *value) __attribute__((nonnull)); static void remove_dirstack_entry_at(variable_T *var, size_t index) __attribute__((nonnull)); static void remove_dirstack_dups(variable_T *var) __attribute__((nonnull)); static bool print_dirstack_entry( bool verbose, size_t plusindex, size_t minusindex, const wchar_t *dir) __attribute__((nonnull)); /* The "pushd" built-in. * -L: don't resolve symbolic links (default) * -P: resolve symbolic links * --default-directory=: go to when the operand is missing * --remove-duplicates: remove duplicate entries in the directory stack. * -L and -P are mutually exclusive: the one specified last is used. */ int pushd_builtin(int argc __attribute__((unused)), void **argv) { bool logical = true, remove_dups = false; const wchar_t *newpwd = L"+1"; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, pushd_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'L': logical = true; break; case L'P': logical = false; break; case L'd': newpwd = xoptarg; break; case L'D': remove_dups = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (!validate_operand_count(argc - xoptind, 0, 1)) return Exit_ERROR; const wchar_t *origpwd = getvar(L VAR_PWD); if (origpwd == NULL) { xerror(0, Ngt("$PWD is not set")); return Exit_FAILURE; } bool useoldpwd = false; if (xoptind < argc) { newpwd = ARGV(xoptind); if (wcscmp(newpwd, L"-") == 0) useoldpwd = true; } size_t stackindex; if (useoldpwd) { newpwd = getvar(L VAR_OLDPWD); stackindex = SIZE_MAX; if (newpwd == NULL || newpwd[0] == L'\0') { xerror(0, Ngt("$OLDPWD is not set")); return Exit_FAILURE; } } else { if (!parse_dirstack_index(newpwd, &stackindex, &newpwd, true)) return Exit_FAILURE; } assert(newpwd != NULL); wchar_t *saveorigpwd = xwcsdup(origpwd); int result = change_directory(newpwd, useoldpwd, logical); #ifndef NDEBUG newpwd = NULL; /* newpwd cannot be used anymore. */ #endif if (result != Exit_SUCCESS) { free(saveorigpwd); return result; } variable_T *var = get_dirstack(); if (var == NULL) { free(saveorigpwd); return Exit_FAILURE; } push_dirstack(var, saveorigpwd); if (stackindex != SIZE_MAX) remove_dirstack_entry_at(var, stackindex); if (remove_dups) remove_dirstack_dups(var); return Exit_SUCCESS; } /* Returns the directory stack. * If the stack is not yet created, it is initialized as an empty stack and * returned. * Fails if a non-array variable or a read-only array already exists, in which * case NULL is returned. */ variable_T *get_dirstack(void) { variable_T *var = search_variable(L VAR_DIRSTACK); if (var != NULL) { if ((var->v_type & VF_MASK) != VF_ARRAY) { xerror(0, Ngt("$%ls is not an array"), L VAR_DIRSTACK); return NULL; } else if (var->v_type & VF_READONLY) { xerror(0, Ngt("$%ls is read-only"), L VAR_DIRSTACK); return NULL; } return var; } // void **ary = xmallocn(1, sizeof *ary); void **ary = xmalloc(sizeof *ary); ary[0] = NULL; return set_array(L VAR_DIRSTACK, 0, ary, SCOPE_GLOBAL, false); } /* Adds the specified value to the directory stack ($DIRSTACK). * `var' must be the return value of a `get_dirstack' call. * `value' is used as an element of the $DIRSTACK array, so the caller must not * modify or free `value' after calling this function. */ void push_dirstack(variable_T *var, wchar_t *value) { size_t index = var->v_valc++; var->v_vals = xrealloce(var->v_vals, index, 2, sizeof *var->v_vals); var->v_vals[index] = value; var->v_vals[index + 1] = NULL; } /* Removes the directory stack entry specified by `index'. * `var' must be the directory stack array and `index' must be less than * `var->v_valc'. */ void remove_dirstack_entry_at(variable_T *var, size_t index) { assert(index < var->v_valc); free(var->v_vals[index]); memmove(&var->v_vals[index], &var->v_vals[index + 1], (var->v_valc - index) * sizeof *var->v_vals); var->v_valc--; } /* Removes directory stack entries that are the same as the current working * directory ($PWD). * `var' must be the return value of a `get_dirstack' call. */ void remove_dirstack_dups(variable_T *var) { const wchar_t *pwd = getvar(L VAR_PWD); if (pwd == NULL) return; for (size_t index = var->v_valc; index-- > 0; ) if (wcscmp(pwd, var->v_vals[index]) == 0) remove_dirstack_entry_at(var, index); } #if YASH_ENABLE_HELP const char pushd_help[] = Ngt( "push a directory into the directory stack" ); const char pushd_syntax[] = Ngt( "\tpushd [-L|-P] [directory]\n" ); #endif /* The "popd" built-in. */ int popd_builtin(int argc, void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, help_option, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } const wchar_t *arg; switch (argc - xoptind) { case 0: arg = L"+0"; break; case 1: arg = ARGV(xoptind); break; default: return too_many_operands_error(1); } variable_T *var = get_dirstack(); if (var == NULL) return Exit_FAILURE; if (var->v_valc == 0) { xerror(0, Ngt("the directory stack is empty")); return Exit_FAILURE; } size_t stackindex; const wchar_t *dummy; if (!parse_dirstack_index(arg, &stackindex, &dummy, true)) return Exit_FAILURE; if (stackindex == SIZE_MAX) { xerror(0, Ngt("`%ls' is not a valid index"), arg); return Exit_ERROR; } if (stackindex < var->v_valc) { remove_dirstack_entry_at(var, stackindex); return Exit_SUCCESS; } int result; wchar_t *newpwd; assert(var->v_valc > 0); var->v_valc--; newpwd = var->v_vals[var->v_valc]; var->v_vals[var->v_valc] = NULL; result = change_directory(newpwd, true, true); free(newpwd); return result; } #if YASH_ENABLE_HELP const char popd_help[] = Ngt( "pop a directory from the directory stack" ); const char popd_syntax[] = Ngt( "\tpopd [index]\n" ); #endif /* Options for the "dirs" built-in. */ const struct xgetopt_T dirs_options[] = { { L'c', L"clear", OPTARG_NONE, true, NULL, }, { L'v', L"verbose", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "dirs" built-in, which accepts the following options: * -c: clear the stack * -v: verbose */ int dirs_builtin(int argc, void **argv) { bool clear = false, verbose = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, dirs_options, XGETOPT_DIGIT)) != NULL) { switch (opt->shortopt) { case L'c': clear = true; break; case L'v': verbose = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (clear) return unset_variable(L VAR_DIRSTACK) ? Exit_FAILURE : Exit_SUCCESS; variable_T *var = search_variable(L VAR_DIRSTACK); bool dirvalid = (var != NULL && (var->v_type & VF_MASK) == VF_ARRAY); size_t size = dirvalid ? var->v_valc : 0; const wchar_t *dir; if (xoptind < argc) { /* print the specified only */ do { size_t index; if (!parse_dirstack_index(ARGV(xoptind), &index, &dir, true)) continue; if (index == SIZE_MAX) { xerror(0, Ngt("`%ls' is not a valid index"), ARGV(xoptind)); continue; } if (!print_dirstack_entry(verbose, size - index, index, dir)) break; } while (++xoptind < argc); } else { /* print all */ dir = getvar(L VAR_PWD); if (dir == NULL) { xerror(0, Ngt("$PWD is not set")); } else { print_dirstack_entry(verbose, 0, size, dir); } if (dirvalid && yash_error_message_count == 0) { for (size_t i = var->v_valc; i-- > 0; ) { if (!print_dirstack_entry(verbose, size - i, i, var->v_vals[i])) break; } } } return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } bool print_dirstack_entry( bool verbose, size_t plusindex, size_t minusindex, const wchar_t *dir) { if (verbose) return xprintf("+%zu\t-%zu\t%ls\n", plusindex, minusindex, dir); else return xprintf("%ls\n", dir); } #if YASH_ENABLE_HELP const char dirs_help[] = Ngt( "print the directory stack" ); const char dirs_syntax[] = Ngt( "\tdirs [-cv] [index...]\n" ); #endif #endif /* YASH_ENABLE_DIRSTACK */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/variable.h000066400000000000000000000163361354143602500143660ustar00rootroot00000000000000/* Yash: yet another shell */ /* variable.h: deals with shell variables and parameters */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_VARIABLE_H #define YASH_VARIABLE_H #include #include "xgetopt.h" extern char **environ; /* variable names */ #define VAR_CDPATH "CDPATH" #define VAR_COLUMNS "COLUMNS" #define VAR_COMMAND_NOT_FOUND_HANDLER "COMMAND_NOT_FOUND_HANDLER" #define VAR_DIRSTACK "DIRSTACK" #define VAR_ECHO_STYLE "ECHO_STYLE" #define VAR_ENV "ENV" #define VAR_FCEDIT "FCEDIT" #define VAR_HANDLED "HANDLED" #define VAR_HISTFILE "HISTFILE" #define VAR_HISTRMDUP "HISTRMDUP" #define VAR_HISTSIZE "HISTSIZE" #define VAR_HOME "HOME" #define VAR_IFS "IFS" #define VAR_LANG "LANG" #define VAR_LC_ALL "LC_ALL" #define VAR_LC_COLLATE "LC_COLLATE" #define VAR_LC_CTYPE "LC_CTYPE" #define VAR_LC_MESSAGES "LC_MESSAGES" #define VAR_LC_MONETARY "LC_MONETARY" #define VAR_LC_NUMERIC "LC_NUMERIC" #define VAR_LC_TIME "LC_TIME" #define VAR_LINENO "LINENO" #define VAR_LINES "LINES" #define VAR_MAIL "MAIL" #define VAR_MAILCHECK "MAILCHECK" #define VAR_MAILPATH "MAILPATH" #define VAR_NLSPATH "NLSPATH" #define VAR_OLDPWD "OLDPWD" #define VAR_OPTARG "OPTARG" #define VAR_OPTIND "OPTIND" #define VAR_PATH "PATH" #define VAR_PPID "PPID" #define VAR_PROMPT_COMMAND "PROMPT_COMMAND" #define VAR_PS1 "PS1" #define VAR_PS2 "PS2" #define VAR_PS4 "PS4" #define VAR_PWD "PWD" #define VAR_RANDOM "RANDOM" #define VAR_TARGETWORD "TARGETWORD" #define VAR_TERM "TERM" #define VAR_WORDS "WORDS" #define VAR_YASH_AFTER_CD "YASH_AFTER_CD" #define VAR_YASH_LE_TIMEOUT "YASH_LE_TIMEOUT" #define VAR_YASH_LOADPATH "YASH_LOADPATH" #define VAR_YASH_VERSION "YASH_VERSION" #define L L"" struct variable_T; struct assign_T; struct command_T; typedef enum path_T { PA_PATH, PA_CDPATH, PA_LOADPATH, PA_count, } path_T; extern unsigned long current_lineno; extern void init_environment(void); extern void init_variables(void); extern char *get_exported_value(const wchar_t *name) __attribute__((nonnull,malloc,warn_unused_result)); typedef enum scope_T { SCOPE_GLOBAL, SCOPE_LOCAL, SCOPE_TEMP, } scope_T; extern _Bool set_variable( const wchar_t *name, wchar_t *value, scope_T scope, _Bool export) __attribute__((nonnull(1))); extern struct variable_T *set_array( const wchar_t *name, size_t count, void **values, scope_T scope, _Bool export) __attribute__((nonnull)); extern _Bool set_array_element( const wchar_t *name, size_t index, wchar_t *value) __attribute__((nonnull)); extern void set_positional_parameters(void *const *values) __attribute__((nonnull)); extern _Bool do_assignments( const struct assign_T *assigns, _Bool temp, _Bool export); struct get_variable_T { enum { GV_NOTFOUND, GV_SCALAR, GV_ARRAY, GV_ARRAY_CONCAT, } type; size_t count; void **values; _Bool freevalues; }; extern const wchar_t *getvar(const wchar_t *name) __attribute__((pure,nonnull)); extern struct get_variable_T get_variable(const wchar_t *name) __attribute__((nonnull,warn_unused_result)); extern void save_get_variable_values(struct get_variable_T *gv) __attribute__((nonnull)); extern void open_new_environment(_Bool temp); extern void close_current_environment(void); extern char **decompose_paths(const wchar_t *paths) __attribute__((malloc,warn_unused_result)); extern char *const *get_path_array(path_T name); extern _Bool define_function(const wchar_t *name, struct command_T *body) __attribute__((nonnull)); extern struct command_T *get_function(const wchar_t *name) __attribute__((nonnull)); #if YASH_ENABLE_DIRSTACK extern _Bool parse_dirstack_index( const wchar_t *restrict indexstr, size_t *restrict indexp, const wchar_t **restrict entryp, _Bool printerror) __attribute__((nonnull)); #endif extern int typeset_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char typeset_help[], typeset_syntax[], export_help[], export_syntax[], local_help[], local_syntax[], readonly_help[], readonly_syntax[]; #endif extern const struct xgetopt_T typeset_options[]; #define local_options (&typeset_options[2]) extern int array_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char array_help[], array_syntax[]; #endif extern const struct xgetopt_T array_options[]; extern int unset_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char unset_help[], unset_syntax[]; #endif extern const struct xgetopt_T unset_options[]; extern int shift_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char shift_help[], shift_syntax[]; #endif extern const struct xgetopt_T shift_options[]; extern int getopts_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char getopts_help[], getopts_syntax[]; #endif extern int read_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char read_help[], read_syntax[]; #endif extern const struct xgetopt_T read_options[]; extern int pushd_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char pushd_help[], pushd_syntax[]; #endif extern const struct xgetopt_T pushd_options[]; #if YASH_ENABLE_DIRSTACK # define cd_options (&pushd_options[1]) # define pwd_options (&pushd_options[2]) #else # define cd_options pushd_options # define pwd_options (&pushd_options[1]) #endif extern int popd_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char popd_help[], popd_syntax[]; #endif extern int dirs_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char dirs_help[], dirs_syntax[]; #endif extern const struct xgetopt_T dirs_options[]; #endif /* YASH_VARIABLE_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/xfnmatch.c000066400000000000000000000517231354143602500144030ustar00rootroot00000000000000/* Yash: yet another shell */ /* xfnmatch.c: regex matching wrapper as a replacement for fnmatch */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "xfnmatch.h" #include #include #include #include #include #include #include "strbuf.h" #include "util.h" struct xfnmatch_T { xfnmflags_T flags; union { regex_t regex; xwcsbuf_T literal; } value; }; /* The flags are logical OR of the followings: * XFNM_SHORTEST: do shortest match * XFNM_HEADONLY: only match at the beginning of the string * XFNM_TAILONLY: only match at the end of the string * XFNM_PERIOD: don't match with a string that starts with a period * XFNM_CASEFOLD: ignore case while matching * XFNM_compiled: use `regex' rather than `literal' * When XFNM_SHORTEST is specified, either (but not both) of XFNM_HEADONLY and * XFNM_TAILONLY must be also specified. When XFNM_PERIOD is specified, * XFNM_HEADONLY must be also specified. */ #define XFNM_HEADTAIL (XFNM_HEADONLY | XFNM_TAILONLY) #define MISMATCH ((xfnmresult_T) { (size_t) -1, (size_t) -1, }) static bool is_matching_pattern_bracket(const wchar_t *pat) __attribute__((nonnull,pure)); static xfnmatch_T *try_compile_literal(const wchar_t *pat, xfnmflags_T flags) __attribute__((malloc,warn_unused_result,nonnull)); static xfnmatch_T *try_compile_regex(const wchar_t *pat, xfnmflags_T flags) __attribute__((malloc,warn_unused_result,nonnull)); static void encode_pattern(const wchar_t *restrict pat, xstrbuf_T *restrict buf) __attribute__((nonnull)); static const wchar_t *encode_pattern_bracket(const wchar_t *restrict pat, xstrbuf_T *restrict buf, mbstate_t *restrict state) __attribute__((nonnull)); static const wchar_t *encode_pattern_bracket2(const wchar_t *restrict pat, xstrbuf_T *restrict buf, mbstate_t *restrict state) __attribute__((nonnull)); static void append_as_collating_symbol(wchar_t c, xstrbuf_T *restrict buf, mbstate_t *restrict state) __attribute__((nonnull)); static xfnmresult_T wmatch_literal( const xfnmatch_T *restrict xfnm, const wchar_t *restrict s) __attribute__((nonnull)); static wchar_t *last_wcsstr( const wchar_t *restrict s, const wchar_t *restrict sub) __attribute__((nonnull)); static xfnmresult_T wmatch_headtail( const regex_t *restrict regex, const wchar_t *restrict s) __attribute__((nonnull)); static xfnmresult_T wmatch_shortest_head( const regex_t *restrict regex, const wchar_t *restrict s) __attribute__((nonnull)); static xfnmresult_T wmatch_shortest_tail( const regex_t *restrict regex, const wchar_t *restrict s) __attribute__((nonnull)); static xfnmresult_T wmatch_longest( const regex_t *restrict regex, const wchar_t *restrict s) __attribute__((nonnull)); /* Checks if there is L'*' or L'?' or a bracket expression in the pattern. * If the result is false, the pattern is not a filename expansion pattern. */ /* This function treats L'/' as an ordinary character. */ bool is_matching_pattern(const wchar_t *pat) { for (;;) { switch (*pat) { case L'\0': return false; case L'\\': pat++; if (*pat == L'\0') return false; pat++; break; case L'*': case L'?': return true; case L'[': if (is_matching_pattern_bracket(pat)) return true; /* falls thru! */ default: pat++; break; } } } /* Checks if the specified string is a syntactically valid bracket expression. * The string must start with L'['. * Backslash escapes are recognized in the bracket expression. */ bool is_matching_pattern_bracket(const wchar_t *pat) { assert(*pat == L'['); pat++; const wchar_t *const savepat = pat; for (;;) { switch (*pat) { case L'\0': return false; case L'[': pat++; const wchar_t *p; switch (*pat) { case L'.': p = wcsstr(&pat[1], L".]"); break; case L':': p = wcsstr(&pat[1], L":]"); break; case L'=': p = wcsstr(&pat[1], L"=]"); break; default: continue; } if (p == NULL) return false; pat = &p[2]; continue; case L']': if (pat > savepat) return true; break; case L'\\': pat++; if (*pat == L'\0') return false; break; } pat++; } } /* Checks if there is L'*' or L'?' or a bracket expression in the pattern. * If the result is false, the pattern is not a filename expansion pattern. * If the pattern contains slashes, components separated by the slashes are each * checked by the `is_matching_pattern' function. */ bool is_pathname_matching_pattern(const wchar_t *pat) { const wchar_t *p; while ((p = wcschr(pat, L'/')) != NULL) { wchar_t buf[p - pat + 1]; wmemcpy(buf, pat, p - pat); buf[p - pat] = L'\0'; if (is_matching_pattern(buf)) return true; pat = &p[1]; } return is_matching_pattern(pat); } /* Compiles the specified pattern. * The flags are logical OR of the followings: * XFNM_SHORTEST: do shortest match * XFNM_HEADONLY: only match at the beginning of string * XFNM_TAILONLY: only match at the end of string * XFNM_PERIOD: force explicit match for a period at the beginning * XFNM_CASEFOLD: ignore case while matching * When XFNM_SHORTEST is specified, either (but not both) of XFNM_HEADONLY and * XFNM_TAILONLY must be also specified. When XFNM_PERIOD is specified, * XFNM_HEADONLY must be also specified. * Returns NULL on failure. */ /* Argument `flags' must not contain XFNM_compiled, XFNM_headstar, or * XFNM_tailstar, which are for internal use only */ xfnmatch_T *xfnm_compile(const wchar_t *pat, xfnmflags_T flags) { if (flags & XFNM_SHORTEST) { if (flags & XFNM_HEADONLY) assert(!(flags & XFNM_TAILONLY)); else assert(flags & XFNM_TAILONLY); } if (flags & XFNM_PERIOD) { assert(flags & XFNM_HEADONLY); if (pat[0] == L'.' || pat[0] == L'\\') flags &= ~XFNM_PERIOD; } if (!(flags & XFNM_CASEFOLD)) { xfnmatch_T *result = try_compile_literal(pat, flags); if (result != NULL) return result; } return try_compile_regex(pat, flags); } /* Checks if the specified pattern is a literal pattern and if so compiles it. * If not a literal pattern, NULL is returned. */ xfnmatch_T *try_compile_literal(const wchar_t *pat, xfnmflags_T flags) { xfnmflags_T oldflags = flags; xfnmatch_T *xfnm = xmalloc(sizeof *xfnm); wb_init(&xfnm->value.literal); while (*pat == L'*') { flags &= ~XFNM_HEADONLY; flags |= XFNM_headstar; pat++; } for (;;) { switch (*pat) { case L'\0': goto success; case L'?': goto fail; case L'*': flags &= ~XFNM_TAILONLY; flags |= XFNM_tailstar; do pat++; while (*pat == L'*'); if (*pat == L'\0') goto success; else goto fail; case L'[': if (wcschr(pat + 1, L']')) goto fail; else goto ordinary; case L'\\': pat++; if (*pat == L'\0') goto success; /* falls thru */ default: ordinary: wb_wccat(&xfnm->value.literal, *pat); break; } pat++; } success: if (oldflags & XFNM_SHORTEST) { if (oldflags & XFNM_HEADONLY) flags &= ~XFNM_tailstar; else flags &= ~XFNM_headstar; } xfnm->flags = flags; return xfnm; fail: wb_destroy(&xfnm->value.literal); free(xfnm); return NULL; } /* Compiles the specified pattern. * Returns NULL on error. */ xfnmatch_T *try_compile_regex(const wchar_t *pat, xfnmflags_T flags) { xstrbuf_T buf; sb_init(&buf); if (flags & XFNM_HEADONLY) sb_ccat(&buf, '^'); encode_pattern(pat, &buf); if (flags & XFNM_TAILONLY) sb_ccat(&buf, '$'); xfnmatch_T *xfnm = xmalloc(sizeof *xfnm); xfnm->flags = flags | XFNM_compiled; int regexflags = 0; if (flags & XFNM_CASEFOLD) regexflags |= REG_ICASE; if ((flags & XFNM_HEADTAIL) == XFNM_HEADTAIL) regexflags |= REG_NOSUB; int err = regcomp(&xfnm->value.regex, buf.contents, regexflags); sb_destroy(&buf); if (err != 0) { free(xfnm); xfnm = NULL; } return xfnm; } /* Converts the specified pathname matching pattern into a regex pattern. * The result is appended to `buf', which must be in the initial shift state. * When this function returns, the buffer is reset to the initial shift state.*/ /* A trailing backslash, escaping the terminating null character, is ignored. * This is useful for pathname expansion since, for example, the pattern * "f*o\/b?r" is separated into "f*o\" and "b?r", one of which has a trailing * backslash that should be ignored. */ void encode_pattern(const wchar_t *restrict pat, xstrbuf_T *restrict buf) { mbstate_t state; memset(&state, 0, sizeof state); /* initial shift state */ for (;;) { switch (*pat) { case L'?': sb_wccat(buf, L'.', &state); break; case L'*': sb_wccat(buf, L'.', &state); sb_wccat(buf, L'*', &state); break; case L'[': pat = encode_pattern_bracket(pat, buf, &state); break; case L'\\': switch (*++pat) { case L'.': case L'*': case L'[': case L'^': case L'$': case L'\\': goto escape; default: goto ordinary; } case L'.': case L'^': case L'$': escape: sb_wccat(buf, L'\\', &state); /* falls thru */ default: ordinary: sb_wccat(buf, *pat, &state); if (*pat == L'\0') return; break; } pat++; } } /* Converts the specified bracket pattern of pathname matching pattern into that * of regex. * Backslash escapes are recognized in the original pattern. * Pointer `pat' must point to the opening bracket '['. * If the bracket pattern was successfully converted, a pointer to the closing * bracket ']' is returned. Otherwise, an escaped bracket character '\[' is * appended to `buf' and `pat' is returned. */ const wchar_t *encode_pattern_bracket(const wchar_t *restrict pat, xstrbuf_T *restrict buf, mbstate_t *restrict state) { const wchar_t *const savepat = pat; size_t const savelength = buf->length; mbstate_t const savestate = *state; assert(*pat == L'['); sb_wccat(buf, L'[', state); pat++; if (*pat == L'!' || *pat == L'^') { sb_wccat(buf, L'^', state); pat++; } if (*pat == L']') { sb_wccat(buf, L']', state); pat++; } for (;;) { switch (*pat) { case L'\0': goto fail; case L'[': pat = encode_pattern_bracket2(pat, buf, state); if (pat == NULL) goto fail; break; case L'\\': pat++; switch (*pat) { case L'\0': goto fail; case L'[': case L'^': case L'-': case L']': append_as_collating_symbol(*pat, buf, state); break; default: sb_wccat(buf, *pat, state); break; } break; default: sb_wccat(buf, *pat, state); if (*pat == L']') return pat; break; } pat++; } fail: sb_truncate(buf, savelength); *state = savestate; sb_wccat(buf, L'\\', state); sb_wccat(buf, L'[', state); return savepat; } /* Converts the collating symbol, equivalence class, or character class pattern * that starts with the opening bracket '[' pointed to by `pat'. * If the pattern is successfully converted, a pointer to the corresponding * closing bracket ']' is returned. If `*pat' is not followed by any of '.', ':' * and '=', then a single bracket character is appended to `buf' and `pat' is * returned. If no corresponding closing bracket is found, NULL is returned. */ const wchar_t *encode_pattern_bracket2(const wchar_t *restrict pat, xstrbuf_T *restrict buf, mbstate_t *restrict state) { const wchar_t *p; assert(*pat == L'['); switch (pat[1]) { case L'.': p = wcsstr(&pat[2], L".]"); break; case L':': p = wcsstr(&pat[2], L":]"); break; case L'=': p = wcsstr(&pat[2], L"=]"); break; default: /* not a valid bracket pattern */ sb_wccat(buf, L'[', state); return pat; } if (p == NULL) return NULL; for (;;) { if (*pat == L'\\') pat++; sb_wccat(buf, *pat, state); if (pat > p) break; pat++; } assert(*pat == L']'); return pat; } void append_as_collating_symbol(wchar_t c, xstrbuf_T *restrict buf, mbstate_t *restrict state) { sb_wccat(buf, L'[', state); sb_wccat(buf, L'.', state); sb_wccat(buf, c, state); sb_wccat(buf, L'.', state); sb_wccat(buf, L']', state); } /* Performs matching on string `s' using pre-compiled pattern `xfnm'. * Returns zero on successful match. On mismatch, the error number that was * returned by `regexec' is returned. * This function does not support the XFNM_SHORTEST flag. The given pattern must * have been compiled without the XFNM_SHORTEST flag. */ int xfnm_match(const xfnmatch_T *restrict xfnm, const char *restrict s) { assert(!(xfnm->flags & XFNM_SHORTEST)); if (xfnm->flags & XFNM_PERIOD) if (s[0] == '.') return REG_NOMATCH; if (xfnm->flags & XFNM_compiled) { return regexec(&xfnm->value.regex, s, 0, NULL, 0); } else { wchar_t *ws = malloc_mbstowcs(s); if (ws != NULL) { xfnmresult_T result = xfnm_wmatch(xfnm, ws); free(ws); if (result.start != (size_t) -1) return 0; } return REG_NOMATCH; } } /* Performs matching on string `s' using pre-compiled pattern `xfnm'. * On match, returns an xfnmresult_T structure which shows the character offsets * of the matched range in `s'. On mismatch, returns { -1, -1 }. * If the pattern was compiled with both XFNM_HEADONLY and XFNM_TAILONLY flags * specified, only the `start' member of the returned structure is meaningful * and the `end' member's value is unspecified. */ xfnmresult_T xfnm_wmatch( const xfnmatch_T *restrict xfnm, const wchar_t *restrict s) { xfnmflags_T flags = xfnm->flags; if (flags & XFNM_PERIOD) { if (s[0] == L'.') return MISMATCH; } if (!(flags & XFNM_compiled)) { return wmatch_literal(xfnm, s); } if ((flags & XFNM_HEADTAIL) == XFNM_HEADTAIL) { return wmatch_headtail(&xfnm->value.regex, s); } if (flags & XFNM_SHORTEST) { if (flags & XFNM_HEADONLY) { assert(!(flags & XFNM_TAILONLY)); return wmatch_shortest_head(&xfnm->value.regex, s); } else { assert(flags & XFNM_TAILONLY); return wmatch_shortest_tail(&xfnm->value.regex, s); } } else { return wmatch_longest(&xfnm->value.regex, s); } } /* Performs matching on string `s' using pre-compiled literal pattern `xfnm'. * See the `xfnm_wmatch' function. */ xfnmresult_T wmatch_literal( const xfnmatch_T *restrict xfnm, const wchar_t *restrict s) { if (xfnm->flags & XFNM_HEADONLY) { s = matchwcsprefix(s, xfnm->value.literal.contents); if (s == NULL) return MISMATCH; if ((xfnm->flags & XFNM_TAILONLY) && (*s != L'\0')) return MISMATCH; size_t slen = xfnm->value.literal.length; if ((xfnm->flags & (XFNM_SHORTEST | XFNM_tailstar)) == XFNM_tailstar) slen += wcslen(s); return (xfnmresult_T) { .start = 0, .end = slen }; } else if (xfnm->flags & XFNM_TAILONLY) { size_t slen = wcslen(s); if (slen < xfnm->value.literal.length) return MISMATCH; size_t index = slen - xfnm->value.literal.length; if (wcscmp(&s[index], xfnm->value.literal.contents) != 0) return MISMATCH; if ((xfnm->flags & (XFNM_SHORTEST | XFNM_headstar)) == XFNM_headstar) index = 0; return (xfnmresult_T) { .start = index, .end = slen }; } else { const wchar_t *ss; switch (xfnm->flags & (XFNM_SHORTEST | XFNM_headstar | XFNM_tailstar)) { case XFNM_headstar: case XFNM_SHORTEST | XFNM_tailstar: ss = last_wcsstr(s, xfnm->value.literal.contents); break; default: ss = wcsstr(s, xfnm->value.literal.contents); break; } if (ss == NULL) return MISMATCH; xfnmresult_T result; if (xfnm->flags & XFNM_headstar) result.start = 0; else result.start = ss - s; if (xfnm->flags & XFNM_tailstar) result.end = wcslen(s); else result.end = (size_t) (ss - s) + xfnm->value.literal.length; return result; } } /* Returns a pointer to the substring of `s' where `sub' last appears in `s'. */ wchar_t *last_wcsstr(const wchar_t *restrict s, const wchar_t *restrict sub) { if (sub[0] == L'\0') return (wchar_t *) s + wcslen(s); wchar_t *lastresult = NULL; for (;;) { wchar_t *result = wcsstr(s, sub); if (result == NULL) break; lastresult = result; s = &result[1]; } return lastresult; } xfnmresult_T wmatch_headtail( const regex_t *restrict regex, const wchar_t *restrict s) { char *mbs = malloc_wcstombs(s); if (mbs == NULL) return MISMATCH; int r = regexec(regex, mbs, 0, NULL, 0); free(mbs); return (r == 0) ? ((xfnmresult_T) { .start = 0 }) : MISMATCH; } xfnmresult_T wmatch_shortest_head( const regex_t *restrict regex, const wchar_t *restrict s) { xstrbuf_T buf; mbstate_t state; size_t i; sb_init(&buf); memset(&state, 0, sizeof state); /* initial shift state */ i = 0; for (;;) { if (regexec(regex, buf.contents, 0, NULL, 0) == 0) { /* successful match */ break; } if (s[i] == L'\0') { /* mismatch */ i = (size_t) -1; break; } if (!sb_wccat(&buf, s[i], &state)) { /* error */ i = (size_t) -1; break; } i++; } sb_destroy(&buf); return (xfnmresult_T) { .start = (i == (size_t) -1) ? -1 : 0, .end = i, }; } xfnmresult_T wmatch_shortest_tail( const regex_t *restrict regex, const wchar_t *restrict s) { size_t i = 0; xfnmresult_T result = MISMATCH; for (;;) { xfnmresult_T newresult = wmatch_longest(regex, &s[i]); if (newresult.start == (size_t) -1) break; newresult.start += i; newresult.end += i; result = newresult; if (s[newresult.start] == L'\0') break; i = newresult.start + 1; } return result; } xfnmresult_T wmatch_longest( const regex_t *restrict regex, const wchar_t *restrict s) { char *mbs = malloc_wcstombs(s); if (mbs == NULL) return MISMATCH; regmatch_t match[1]; int r = regexec(regex, mbs, 1, match, 0); if (r != 0) { free(mbs); return MISMATCH; } /* Now convert the byte offsets into the character offsets */ const char *mbs2; mbstate_t state; xfnmresult_T result; memset(&state, 0, sizeof state); /* initial shift state */ mbs[match[0].rm_eo] = '\0'; mbs2 = mbs; result.end = mbsrtowcs(NULL, &mbs2, 0, &state); memset(&state, 0, sizeof state); mbs[match[0].rm_so] = '\0'; mbs2 = mbs; result.start = mbsrtowcs(NULL, &mbs2, 0, &state); if (result.start == (size_t) -1 || result.end == (size_t) -1) result = MISMATCH; free(mbs); return result; } /* Substitutes part of string `s' that matches pre-compiled pattern `xfnm' * with string `repl'. If `substall' is true, all matching substrings in `s' are * substituted. Otherwise, only the first match is substituted. The resulting * string is returned as a newly-malloced string. */ wchar_t *xfnm_subst(const xfnmatch_T *restrict xfnm, const wchar_t *restrict s, const wchar_t *restrict repl, bool substall) { xfnmflags_T flags = xfnm->flags; if ((flags & XFNM_HEADTAIL) == XFNM_HEADTAIL) { xfnmresult_T result; if (flags & XFNM_compiled) result = wmatch_headtail(&xfnm->value.regex, s); else result = wmatch_literal(xfnm, s); return xwcsdup((result.start != (size_t) -1) ? repl : s); } if (flags & XFNM_HEADONLY) substall = false; xwcsbuf_T buf; size_t i = 0; wb_init(&buf); do { xfnmresult_T result = xfnm_wmatch(xfnm, &s[i]); if (result.start == (size_t) -1 || result.start >= result.end) break; wb_ncat(&buf, &s[i], result.start); wb_cat(&buf, repl); i += result.end; } while (substall); return wb_towcs(wb_cat(&buf, &s[i])); } /* Frees the specified compiled pattern. */ void xfnm_free(xfnmatch_T *xfnm) { if (xfnm != NULL) { if (xfnm->flags & XFNM_compiled) regfree(&xfnm->value.regex); else wb_destroy(&xfnm->value.literal); free(xfnm); } } /* Tests if pattern matching expression `pattern' matches string `s'. */ bool match_pattern(const wchar_t *s, const wchar_t *pattern) { xfnmatch_T *xfnm = xfnm_compile(pattern, XFNM_HEADONLY | XFNM_TAILONLY); if (xfnm == NULL) return false; bool match = (xfnm_wmatch(xfnm, s).start != (size_t) -1); xfnm_free(xfnm); return match; } #if YASH_ENABLE_TEST /* Tests if extended regular expression `regex' matches string `s'. */ bool match_regex(const wchar_t *s, const wchar_t *regex) { regex_t compiled_regex; char *mbs_regex = malloc_wcstombs(regex); int err = regcomp(&compiled_regex, mbs_regex, REG_EXTENDED | REG_NOSUB); free(mbs_regex); if (err != 0) return false; char *mbs_s = malloc_wcstombs(s); err = regexec(&compiled_regex, mbs_s, 0, NULL, 0); free(mbs_s); regfree(&compiled_regex); return err == 0; } #endif /* YASH_ENABLE_TEST */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/xfnmatch.h000066400000000000000000000043531354143602500144050ustar00rootroot00000000000000/* Yash: yet another shell */ /* xfnmatch.h: regex matching wrapper as a replacement for fnmatch */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_XFNMATCH_H #define YASH_XFNMATCH_H #include typedef struct xfnmatch_T xfnmatch_T; typedef enum { XFNM_SHORTEST = 1 << 0, XFNM_HEADONLY = 1 << 1, XFNM_TAILONLY = 1 << 2, XFNM_PERIOD = 1 << 3, XFNM_CASEFOLD = 1 << 4, XFNM_compiled = 1 << 5, XFNM_headstar = 1 << 6, XFNM_tailstar = 1 << 7, } xfnmflags_T; typedef struct { size_t start, end; } xfnmresult_T; extern _Bool is_matching_pattern(const wchar_t *pat) __attribute__((pure,nonnull)); extern _Bool is_pathname_matching_pattern(const wchar_t *pat) __attribute__((pure,nonnull)); extern xfnmatch_T *xfnm_compile(const wchar_t *pat, xfnmflags_T flags) __attribute__((malloc,warn_unused_result,nonnull)); extern int xfnm_match( const xfnmatch_T *restrict xfnm, const char *restrict s) __attribute__((nonnull)); extern xfnmresult_T xfnm_wmatch( const xfnmatch_T *restrict xfnm, const wchar_t *restrict s) __attribute__((nonnull)); extern wchar_t *xfnm_subst( const xfnmatch_T *restrict xfnm, const wchar_t *restrict s, const wchar_t *restrict repl, _Bool substall) __attribute__((malloc,warn_unused_result,nonnull)); extern void xfnm_free(xfnmatch_T *xfnm); extern _Bool match_pattern(const wchar_t *s, const wchar_t *pattern) __attribute__((nonnull)); #if YASH_ENABLE_TEST extern _Bool match_regex(const wchar_t *s, const wchar_t *regex) __attribute__((nonnull)); #endif #endif /* YASH_XFNMATCH_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/xgetopt.c000066400000000000000000000366031354143602500142650ustar00rootroot00000000000000/* Yash: yet another shell */ /* xgetopt.c: command option parser */ /* (C) 2012-2013 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "xgetopt.h" #include #include #include #if LIST_AMBIGUOUS_OPTIONS # include #endif #include #include #include "option.h" #include "util.h" static struct xgetopt_T *search_argv(void); static struct xgetopt_T *parse_short_option(void); static struct xgetopt_T *found_short_option( const struct xgetopt_T *restrict opt) __attribute__((nonnull)); static struct xgetopt_T *parse_long_option(void); static struct xgetopt_T *found_long_option(const wchar_t *eq, const struct xgetopt_T *restrict opt) __attribute__((nonnull)); static struct xgetopt_T *finish_parsing_current_argument( const struct xgetopt_T *restrict opt) __attribute__((nonnull)); static struct xgetopt_T *parse_separate_option_argument(bool shortopt, const struct xgetopt_T *restrict opt) __attribute__((nonnull)); static void argshift(void); static struct xgetopt_T *done(void); static struct xgetopt_T *no_such_option(const wchar_t *s) __attribute__((nonnull)); static struct xgetopt_T *ambiguous_long_option(const wchar_t *s, size_t namelen) __attribute__((nonnull)); static struct xgetopt_T *option_argument_is_missing( bool shortopt, const struct xgetopt_T *opt) __attribute__((nonnull)); static struct xgetopt_T *unwanted_long_option_argument( const wchar_t *s, const struct xgetopt_T *opt) __attribute__((nonnull)); static struct xgetopt_T *sentinel(void) __attribute__((pure)); /* When an option that takes an argument was parsed, a pointer to the argument * is assigned to this variable. */ wchar_t *xoptarg; /* A pointer to the NULL-terminated array of pointers to wide strings that are * being parsed. */ static void **argv; /* The index to array `argv' to which the next found option should be moved. * In other words, `xoptind - 1' is the index of the last parsed option string * (or its argument). */ int xoptind = 0; /* The index of the element in array `argv' that should be parsed next. */ static int argvindex; /* The index of the character in the currently parsed argument string that * should be parsed next. */ static int charindex; /* An array of xgetopt_T's that specifies the set of options accepted by the * parser. */ static const struct xgetopt_T *opts; /* A set of option flags for the current parsing. */ static enum xgetoptopt_T getoptopt; /* Parses options for a command. * * When starting parsing of a new set of arguments, firstly `xoptind' must be * set to 0. Then, this function must be repeatedly called with the same * arguments. One option is parsed for each call. * * `argv_' is a pointer to a NULL-terminated array whose elements are pointers * to wide strings that are parsed. The array elements are reordered while * parsing so that all the options come before the operands unless doing the * POSIX parsing. * `opts_' is a pointer to an array of xgetopt_T structures that specify options * accepted by the parser. The array must terminated by a xgetopt_T structure * whose `shortopt' member is L'\0'. * `getoptopt_' is bitwise OR of zero or more xgetoptopt_T flags that specify * the way parsing is done: * XGETOPT_POSIX: Do the POSIX parsing, that is, parse options before the * first operand only. * XGETOPT_DIGIT: Negative numbers like "-1" are not treated as options. * In the POSIXly correct mode, XGETOPT_POSIX is always assumed and * XGETOPT_DIGIT is ignored. * * When a valid option is parsed, this function returns a pointer to the * xgetopt_T structure (in the `opts_' array) corresponding to the parsed * option. * Moreover, if the option takes an argument, `xoptarg' is assigned a pointer to * the argument; Otherwise, `xoptarg' is set to NULL. * * When an option is parsed but it is not in the `opts_' array or a required * option argument is missing, this function prints an error message and returns * a pointer to the last xgetopt_T structure in `opts_', whose `shortopt' member * is L'\0'. * * When there is no more option, a NULL pointer is returned and `xoptind' is set * to the index of the first operand in the `argv_' array. (If there are no * operands, `xoptind' is the index of the terminating NULL pointer in `argv_'.) * * The xgetopt_T structure contains the following members: * shortopt: A character representing a single-character option. * If `shortopt' is L'-', it is not parsed as a single-char option. * longopt: A pointer to a string representing a long option (not including * the preceding "--"). If `longopt' is a NULL pointer, it is not * parsed as a long option. * optarg: One of the optarg_T values, which specify if the option takes an * argument. * posix: In the POSIXly correct mode, this flag must be true for the * option to be accepted. * ptr: Not used in the `xgetopt' function. */ struct xgetopt_T *xgetopt( void **restrict argv_, const struct xgetopt_T *restrict opts_, enum xgetoptopt_T getoptopt_) { if (xoptind == 0) { argv = argv_; opts = opts_; getoptopt = getoptopt_; xoptind = argvindex = charindex = 1; /* reset the state */ } /* sanity check */ assert(argv_ == argv); assert(opts_ == opts); assert(getoptopt_ == getoptopt); assert(xoptind <= argvindex); return search_argv(); } /* Searches `argv' to find a candidate of an option. */ struct xgetopt_T *search_argv(void) { enum xgetoptopt_T opt = posixly_correct ? XGETOPT_POSIX : getoptopt; const wchar_t *arg; for (; (arg = argv[argvindex]) != NULL; argvindex++) { if (arg[0] == L'-') { if (arg[1] == L'-') /* `arg' starts with "--" */ return parse_long_option(); if (arg[1] != L'\0') if (!(opt & XGETOPT_DIGIT) || !iswdigit(arg[1])) return parse_short_option(); } /* `arg' is not a option */ if (opt & XGETOPT_POSIX) break; } /* no more options! */ return done(); } /* Parses `argv[argvindex]' as single-character options. */ struct xgetopt_T *parse_short_option(void) { const wchar_t *arg = argv[argvindex]; assert((size_t) charindex < wcslen(arg)); /* A hyphen should not be regarded as an option because the hyphen character * is used as a sentinel in the xgetopt_T structure to indicate that a * single-character option is not available. */ if (arg[charindex] == L'-') return no_such_option(arg); for (const struct xgetopt_T *opt = opts; opt->shortopt != L'\0'; opt++) if (opt->posix || !posixly_correct) if (opt->shortopt == arg[charindex]) return found_short_option(opt); return no_such_option(arg); } /* This function is called when a single-character option was found. * `opt' is a pointer to the option in the xgetopt_T array, which is returned by * this function unless an option argument is expected but missing. */ struct xgetopt_T *found_short_option(const struct xgetopt_T *restrict opt) { const wchar_t *arg = argv[argvindex]; if (opt->optarg == OPTARG_NONE) { /* the option doesn't take an argument */ if (arg[charindex + 1] != L'\0') { /* we have a next option in the same argument string */ charindex++; return (struct xgetopt_T *) opt; } else { /* no options are remaining in this argument string */ return finish_parsing_current_argument(opt); } } else { /* the option takes an argument */ xoptarg = (wchar_t *) &arg[charindex + 1]; if (*xoptarg == L'\0' && opt->optarg == OPTARG_REQUIRED) { /* the option argument is split from the option like "-x arg" */ return parse_separate_option_argument(true, opt); } else { /* the option argument is in the same string like "-xarg" */ return finish_parsing_current_argument(opt); } } } /* Parses `argv[argvindex]' as a long option. */ struct xgetopt_T *parse_long_option(void) { const wchar_t *arg = argv[argvindex]; if (arg[2] == L'\0') { /* `arg' is "--" */ argshift(); return done(); } if (posixly_correct) return no_such_option(arg); /* identify the long option */ const struct xgetopt_T *match = NULL; size_t namelen = wcscspn(&arg[2], L"="); for (const struct xgetopt_T *opt = opts; opt->shortopt != L'\0'; opt++) { if (!opt->posix && posixly_correct) continue; if (opt->longopt == NULL) continue; if (wcsncmp(opt->longopt, &arg[2], namelen) != 0) continue; if (opt->longopt[namelen] == L'\0') { /* exact match */ match = opt; break; } /* partial match: `&arg[2]' starts with `opt->longopt' */ if (match == NULL) /* first partial match */ match = opt; else /* more than one partial match */ return ambiguous_long_option(arg, namelen); } if (match == NULL) return no_such_option(arg); return found_long_option(&arg[namelen + 2], match); } /* This function is called when a long option was found. * `eq' is a pointer to the first L'=' in `argv[argvindex]' (or the terminating * null character if there is no L'='). * `opt' is a pointer to the option in the xgetopt_T array, which is returned by * this function unless an option argument is expected but missing. */ struct xgetopt_T *found_long_option(const wchar_t *eq, const struct xgetopt_T *restrict opt) { if (opt->optarg == OPTARG_NONE) { /* the option doesn't take an argument */ if (*eq != L'\0') return unwanted_long_option_argument(argv[argvindex], opt); return finish_parsing_current_argument(opt); } /* the option takes an argument */ if (*eq != L'\0') { /* the argument is specified after L'=' like "--option=argument" */ xoptarg = (wchar_t *) &eq[1]; return finish_parsing_current_argument(opt); } /* no option argument in `argv[argvindex]' */ switch (opt->optarg) { case OPTARG_OPTIONAL: /* the optional argument is not given */ return finish_parsing_current_argument(opt); case OPTARG_REQUIRED: /* the argument is split from the option like "--option argument" */ return parse_separate_option_argument(false, opt); case OPTARG_NONE: assert(false); } assert(false); } /* This function is called when an option was parsed in an argument string and * the argument has no more options to be parsed. * Before calling this function, `argvindex' must be set to the index of the * argument in `argv' that was just parsed. This function moves * `argv[argvindex]' to `argv[xoptind]' using the `argshift' function and then * increments `argvindex' and `xoptind'. * `charindex' is reset to 1. * `opt' is the option that was parsed, which is returned by this function. */ struct xgetopt_T *finish_parsing_current_argument( const struct xgetopt_T *restrict opt) { argshift(); charindex = 1; return (struct xgetopt_T *) opt; } /* This function is called when an option that takes an argument was found and * the argument is separate from the option string. * `shortopt' must be true iff the option is a single-character option. * `xoptarg' is set to `argv[argvindex+1]', which is supposed to be the option * argument. `argv[argvindex]' and `argv[argvindex+1]' are moved to * `argv[xoptind]' and `argv[xoptind+1]', respectively, and then `argvindex' and `xoptind' are each incremented by 2. * `charindex' is reset to 1. * `opt' is the option that was parsed, which is returned by this function * unless the argument is missing. */ struct xgetopt_T *parse_separate_option_argument(bool shortopt, const struct xgetopt_T *restrict opt) { xoptarg = argv[argvindex + 1]; if (xoptarg == NULL) return option_argument_is_missing(shortopt, opt); argshift(); argshift(); charindex = 1; return (struct xgetopt_T *) opt; } /* Reorders array elements. The `argv[argvindex]' is moved to `argv[xoptind]'. * Elements `argv[argvindex+1]', `argv[argvindex+2]', ..., `argv[xoptind]' are * moved to `argv[argvindex]', `argv[argvindex+1]', ..., `argv[xoptind-1]', * respectively. After reordering, `argvindex' and `xoptind' are each * incremented by 1. */ void argshift(void) { void *s = argv[argvindex]; assert(argvindex >= xoptind); for (int i = argvindex; i > xoptind; i--) argv[i] = argv[i - 1]; argv[xoptind] = s; argvindex++, xoptind++; } /* This function is called when there is no more option to be parsed. * Returns NULL. */ struct xgetopt_T *done(void) { #ifndef NDEBUG argv = NULL; opts = NULL; #endif return NULL; } /* Prints an error message that says that string `s' is not a valid option. * Returns the sentinel value that indicates an error. */ struct xgetopt_T *no_such_option(const wchar_t *s) { xerror(0, Ngt("`%ls' is not a valid option"), s); return sentinel(); } /* Prints an error message that says that string `s' is an ambiguous option. * `namelen' is the length of the long option name in `s', not including the * beginning "--", up to (but not including) L'=' or the end of the string. * Returns the sentinel value that indicates an error. */ struct xgetopt_T *ambiguous_long_option(const wchar_t *s, size_t namelen) { xerror(0, Ngt("option `%ls' is ambiguous"), s); #if LIST_AMBIGUOUS_OPTIONS for (const struct xgetopt_T *opt = opts; opt->shortopt != L'\0'; opt++) if (opt->longopt != NULL && wcsncmp(opt->longopt, &s[2], namelen) == 0) fprintf(stderr, "\t--%ls\n", opt->longopt); #else (void) namelen; #endif return sentinel(); } /* Prints an error message that says that an option argument is missing. * `shortopt' must be true iff the erroneous option is a single-character * option. * `opt' is a pointer to the erroneous option in the array given to the * `xgetopt' function. * Returns the sentinel value that indicates an error. */ struct xgetopt_T *option_argument_is_missing( bool shortopt, const struct xgetopt_T *opt) { if (shortopt) xerror(0, Ngt("the -%lc option requires an argument"), (wint_t) opt->shortopt); else xerror(0, Ngt("the --%ls option requires an argument"), opt->longopt); return sentinel(); } /* Prints an error message that says that a long option that does not take an * argument was specified with an argument. * `s' is a pointer to the erroneous argument string. * `opt' is a pointer to the erroneous option in the array given to the * `xgetopt' function. * Returns the sentinel value that indicates an error. */ struct xgetopt_T *unwanted_long_option_argument( const wchar_t *s, const struct xgetopt_T *opt) { xerror(0, Ngt("%ls: the --%ls option does not take an argument"), s, opt->longopt); return sentinel(); } /* Returns a pointer to the sentinel element of the specified xgetopt_T array. * A sentinel element is the first element whose `shortopt' is L'\0', which * indicates the end of the array. */ struct xgetopt_T *sentinel(void) { const struct xgetopt_T *opt = opts; while (opt->shortopt != L'\0') opt++; return (struct xgetopt_T *) opt; } /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/xgetopt.h000066400000000000000000000025151354143602500142650ustar00rootroot00000000000000/* Yash: yet another shell */ /* xgetopt.h: command option parser */ /* (C) 2012 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_XGETOPT_H #define YASH_XGETOPT_H #include extern wchar_t *xoptarg; extern int xoptind; enum xgetoptopt_T { XGETOPT_POSIX = 1 << 0, XGETOPT_DIGIT = 1 << 1, }; enum optarg_T { OPTARG_NONE, OPTARG_REQUIRED, OPTARG_OPTIONAL, }; struct xgetopt_T { wchar_t shortopt; const wchar_t *longopt; enum optarg_T optarg; _Bool posix; void *ptr; }; extern struct xgetopt_T *xgetopt( void **restrict argv_, const struct xgetopt_T *restrict opts_, enum xgetoptopt_T getoptopt_) __attribute__((nonnull)); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/yash.c000066400000000000000000000441141354143602500135330ustar00rootroot00000000000000/* Yash: yet another shell */ /* yash.c: basic functions of the shell */ /* (C) 2007-2018 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "common.h" #include "yash.h" #include #include #include #if HAVE_GETTEXT # include #endif #include #include #include #include #include #include #include #include "alias.h" #include "builtin.h" #include "configm.h" #include "exec.h" #include "expand.h" #if YASH_ENABLE_HISTORY # include "history.h" #endif #include "input.h" #include "job.h" #include "option.h" #include "parser.h" #include "path.h" #include "redir.h" #include "sig.h" #include "strbuf.h" #include "util.h" #include "variable.h" extern int main(int argc, char **argv) __attribute__((nonnull)); static struct input_file_info_T *new_input_file_info(int fd, size_t bufsize) __attribute__((malloc,warn_unused_result)); static void execute_profile(const wchar_t *profile); static void execute_rcfile(const wchar_t *rcfile); static bool execute_file_in_home(const wchar_t *path) __attribute__((nonnull)); static bool execute_file(const wchar_t *path); static bool execute_file_mbs(const char *path); static void print_help(void); static void print_version(void); static void parse_and_exec(struct parseparam_T *pinfo, bool finally_exit) __attribute__((nonnull(1))); static bool input_is_interactive_terminal(const parseparam_T *pinfo) __attribute__((nonnull)); /* The process ID of the shell. * This value does not change in a subshell. */ pid_t shell_pid; /* The process group ID of the shell. * In a job-controlled subshell, this value is set to the subshell's pgid. */ pid_t shell_pgid; /* Set to true when the shell has been initialized. */ bool shell_initialized; /* If this flag is true when the "exit" built-in is invoked, the -f option is * assumed to be specified. */ static bool forceexit; /* The next value of `forceexit'. */ bool nextforceexit; /* The `input_file_info_T' structure for reading from the standard input. */ struct input_file_info_T *stdin_input_file_info; /* The "main" function. The execution of the shell starts here. */ int main(int argc, char **argv) { void *wargv[argc + 1]; const wchar_t *shortest_name; setvbuf(stdout, NULL, _IOLBF, BUFSIZ); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); setlocale(LC_ALL, ""); #if HAVE_GETTEXT bindtextdomain(PACKAGE_NAME, LOCALEDIR); textdomain(PACKAGE_NAME); #endif /* convert arguments into wide strings */ for (int i = 0; i < argc; i++) { wargv[i] = malloc_mbstowcs(argv[i]); if (wargv[i] == NULL) { fprintf(stderr, gt("%s: cannot convert the argument `%s' " "into a wide character string"), argv[0], argv[i]); fprintf(stderr, gt("%s: the argument is replaced with an empty string\n"), argv[0]); wargv[i] = xwcsdup(L""); } } wargv[argc] = NULL; /* parse argv[0] */ yash_program_invocation_name = wargv[0]; yash_program_invocation_short_name = wcsrchr(yash_program_invocation_name, L'/'); if (yash_program_invocation_short_name != NULL) yash_program_invocation_short_name++; else yash_program_invocation_short_name = yash_program_invocation_name; command_name = yash_program_invocation_name; is_login_shell = (yash_program_invocation_name[0] == L'-'); shortest_name = yash_program_invocation_short_name; if (shortest_name[0] == L'-') shortest_name++; if (wcscmp(shortest_name, L"sh") == 0) posixly_correct = true; shell_pid = getpid(); shell_pgid = getpgrp(); stdin_input_file_info = new_input_file_info(STDIN_FILENO, 1); init_cmdhash(); init_homedirhash(); init_environment(); init_signal(); init_shellfds(); init_job(); init_builtin(); init_alias(); struct shell_invocation_T options = { .profile = NULL, .rcfile = NULL, }; int optresult = parse_shell_options(argc, wargv, &options); if (optresult != Exit_SUCCESS) exit(optresult); if (options.version) print_version(); if (options.help) print_help(); if (options.version || options.help) exit(yash_error_message_count == 0 ? Exit_SUCCESS : Exit_FAILURE); init_variables(); union { wchar_t *command; int fd; } input; const char *inputname; if (shopt_cmdline && shopt_stdin) exit(mutually_exclusive_option_error(L'c', L's')); if (shopt_cmdline) { input.command = wargv[xoptind++]; if (input.command == NULL) { xerror(0, Ngt("the -c option is specified " "but no command is given")); exit(Exit_ERROR); } if (xoptind < argc) { inputname = argv[xoptind]; command_name = wargv[xoptind++]; } else { inputname = posixly_correct ? "sh -c" : "yash -c"; } } else { if (argc == xoptind) shopt_stdin = true; if (shopt_stdin) { input.fd = STDIN_FILENO; inputname = NULL; if (!options.is_interactive_set && argc == xoptind && isatty(STDIN_FILENO) && isatty(STDERR_FILENO)) is_interactive = true; unset_nonblocking(STDIN_FILENO); } else { inputname = argv[xoptind]; command_name = wargv[xoptind]; xoptind++; input.fd = move_to_shellfd(open(inputname, O_RDONLY)); if (input.fd < 0) { int errno_ = errno; xerror(errno_, Ngt("cannot open file `%s'"), inputname); exit(errno_ == ENOENT ? Exit_NOTFOUND : Exit_NOEXEC); } } } #if YASH_ENABLE_LINEEDIT /* enable line editing if interactive and connected to a terminal */ if (!options.lineedit_set && shopt_lineedit == SHOPT_NOLINEEDIT) if (is_interactive && isatty(STDIN_FILENO) && isatty(STDERR_FILENO)) set_lineedit_option(SHOPT_VI); #endif is_interactive_now = is_interactive; if (!options.do_job_control_set) do_job_control = is_interactive; if (do_job_control) { open_ttyfd(); if (do_job_control) ensure_foreground(); } set_signals(); set_positional_parameters(&wargv[xoptind]); if (is_login_shell && !posixly_correct && !options.noprofile) if (getuid() == geteuid() && getgid() == getegid()) execute_profile(options.profile); if (is_interactive && !options.norcfile) if (getuid() == geteuid() && getgid() == getegid()) execute_rcfile(options.rcfile); shell_initialized = true; if (shopt_cmdline) exec_wcs(input.command, inputname, true); else exec_input(input.fd, inputname, XIO_SUBST_ALIAS | XIO_FINALLY_EXIT | (is_interactive ? XIO_INTERACTIVE : 0)); assert(false); } struct input_file_info_T *new_input_file_info(int fd, size_t bufsize) { struct input_file_info_T *info = xmallocs(sizeof *info, bufsize, sizeof *info->buf); info->fd = fd; info->bufpos = info->bufmax = 0; info->bufsize = bufsize; memset(&info->state, 0, sizeof info->state); // initial shift state return info; } /* Executes "$HOME/.yash_profile". */ void execute_profile(const wchar_t *profile) { if (profile != NULL) execute_file(profile); else execute_file_in_home(L".yash_profile"); } /* Executes the initialization file. * `rcfile' is the filename to source. * If `rcfile' is NULL, it defaults to "$HOME/.yashrc" and if the file cannot be * read it falls back to "initialization/default" from $YASH_LOADPATH. * In the POSIXly-correct mode, `rcfile' is ignored and "$ENV" is used. */ void execute_rcfile(const wchar_t *rcfile) { if (posixly_correct) { const wchar_t *env = getvar(L VAR_ENV); if (env == NULL) return; wchar_t *path = parse_and_expand_string(env, "$ENV", false); execute_file(path); free(path); return; } if (rcfile != NULL) { execute_file(rcfile); return; } if (execute_file_in_home(L".yashrc")) return; char *path = which("initialization/default", get_path_array(PA_LOADPATH), is_readable_regular); execute_file_mbs(path); free(path); } /* Executes the specified file. The `path' must be relative to $HOME. */ bool execute_file_in_home(const wchar_t *path) { const wchar_t *home = getvar(L VAR_HOME); if (home == NULL || home[0] == L'\0') return false; xwcsbuf_T fullpath; wb_init(&fullpath); wb_cat(&fullpath, home); if (fullpath.contents[fullpath.length - 1] != L'/') wb_wccat(&fullpath, L'/'); wb_cat(&fullpath, path); bool executed = execute_file(fullpath.contents); wb_destroy(&fullpath); return executed; } /* Executes the specified file if `path' is non-NULL. * Returns true iff the file was found. */ bool execute_file(const wchar_t *path) { if (path == NULL) return false; char *mbspath = malloc_wcstombs(path); bool executed = execute_file_mbs(mbspath); free(mbspath); return executed; } /* Executes the specified file if `path' is non-NULL. * Returns true iff the file was found. */ bool execute_file_mbs(const char *path) { if (path == NULL) return false; int fd = move_to_shellfd(open(path, O_RDONLY)); if (fd < 0) return false; exec_input(fd, path, XIO_SUBST_ALIAS); cancel_return(); remove_shellfd(fd); xclose(fd); return true; } /* Exits the shell with the specified exit status. * When this function is first called and `status' is negative, the value of * `laststatus' is used for `status'. When this function is called again and * `status' is negative, the value for the first call is used. * This function executes the EXIT trap. * This function never returns. * This function is reentrant and exits immediately if reentered. */ void exit_shell_with_status(int status) { if (status >= 0) laststatus = status; assert(laststatus >= 0); if (exitstatus < 0) { exitstatus = laststatus; execute_exit_trap(); } else { if (status >= 0) exitstatus = status; } #if YASH_ENABLE_HISTORY finalize_history(); #endif exit(exitstatus); } /* Prints the help message to the standard output. */ void print_help(void) { #if YASH_ENABLE_HELP const char *shell_name = posixly_correct ? "sh" : "yash"; xprintf(gt( "Syntax:\n" "\t%s [option...] [filename [argument...]]\n" "\t%s [option...] -c command [command_name [argument...]]\n" "\t%s [option...] -s [argument...]\n"), shell_name, shell_name, shell_name); xprintf("\n"); print_shopts(true); xprintf(gt("Try `man yash' for details.\n")); #endif /* YASH_ENABLE_HELP */ } /* Prints the version info to the standard output. */ void print_version(void) { xprintf(gt("Yet another shell, version %s\n"), PACKAGE_VERSION); xprintf(PACKAGE_COPYRIGHT "\n"); xprintf(gt("This is free software licensed under GNU GPL version 2.\n" "You can modify and redistribute it, but there is NO WARRANTY.\n")); if (shopt_verbose) { xprintf(gt("\nEnabled features:\n")); xprintf("" #ifndef NDEBUG " * DEBUG\n" #endif #if YASH_ENABLE_ARRAY " * array\n" #endif #if YASH_ENABLE_DIRSTACK " * dirstack\n" #endif #if YASH_ENABLE_HELP " * help\n" #endif #if YASH_ENABLE_HISTORY " * history\n" #endif #if YASH_ENABLE_LINEEDIT " * lineedit\n" #endif #if HAVE_GETTEXT " * nls\n" #endif #if YASH_ENABLE_PRINTF " * printf/echo\n" #endif #if YASH_ENABLE_SOCKET " * socket\n" #endif #if YASH_ENABLE_TEST " * test\n" #endif #if YASH_ENABLE_ULIMIT " * ulimit\n" #endif ); } } /********** Functions to Execute Commands **********/ /* Parses the specified wide string and executes it as commands. * `name' is printed in an error message on syntax error. `name' may be NULL. * If there are no commands in `code', `laststatus' is set to zero. */ void exec_wcs(const wchar_t *code, const char *name, bool finally_exit) { struct input_wcs_info_T iinfo = { .src = code, }; struct parseparam_T pinfo = { .print_errmsg = true, .enable_verbose = false, .enable_alias = true, .filename = name, .lineno = 1, .input = input_wcs, .inputinfo = &iinfo, .interactive = false, }; parse_and_exec(&pinfo, finally_exit); } /* Parses the input from the specified file descriptor and executes commands. * The file descriptor must be either STDIN_FILENO or a shell FD. If the file * descriptor is STDIN_FILENO, XIO_FINALLY_EXIT must be specified in `options'. * If `name' is non-NULL, it is printed in an error message on syntax error. * If XIO_INTERACTIVE is specified, the input is considered interactive. * If there are no commands in the input, `laststatus' is set to zero. */ void exec_input(int fd, const char *name, exec_input_options_T options) { struct parseparam_T pinfo = { .print_errmsg = true, .enable_verbose = true, .enable_alias = options & XIO_SUBST_ALIAS, .filename = name, .lineno = 1, .interactive = options & XIO_INTERACTIVE, }; struct input_interactive_info_T intrinfo; struct input_file_info_T *inputinfo; if (fd == STDIN_FILENO) inputinfo = stdin_input_file_info; else inputinfo = new_input_file_info(fd, BUFSIZ); if (pinfo.interactive) { intrinfo.fileinfo = inputinfo; intrinfo.prompttype = 1; pinfo.input = input_interactive; pinfo.inputinfo = &intrinfo; } else { pinfo.input = input_file; pinfo.inputinfo = inputinfo; } parse_and_exec(&pinfo, options & XIO_FINALLY_EXIT); assert(inputinfo != stdin_input_file_info); free(inputinfo); } /* Parses the input using the specified `parseparam_T' and executes commands. * If no commands were executed, `laststatus' is set to Exit_SUCCESS. */ void parse_and_exec(parseparam_T *pinfo, bool finally_exit) { bool executed = false; if (pinfo->interactive) disable_return(); for (;;) { if (pinfo->interactive) { set_laststatus_if_interrupted(); forceexit = nextforceexit; nextforceexit = false; pinfo->lineno = 1; } else { if (need_break()) goto out; } and_or_T *commands; switch (read_and_parse(pinfo, &commands)) { case PR_OK: if (commands != NULL) { if (shopt_exec || is_interactive) { exec_and_or_lists(commands, finally_exit && !pinfo->interactive && pinfo->lastinputresult == INPUT_EOF); executed = true; } andorsfree(commands); } break; case PR_EOF: if (!executed) laststatus = Exit_SUCCESS; if (!finally_exit) goto out; if (shopt_ignoreeof && input_is_interactive_terminal(pinfo)) { fprintf(stderr, gt("Use `exit' to leave the shell.\n")); } else { wchar_t argv0[] = L"EOF"; exit_builtin(1, (void *[]) { argv0, NULL }); } break; case PR_SYNTAX_ERROR: if (shell_initialized && !is_interactive_now) exit_shell_with_status(Exit_SYNERROR); if (!pinfo->interactive) { laststatus = Exit_SYNERROR; goto out; } break; case PR_INPUT_ERROR: laststatus = Exit_ERROR; goto out; } } out: if (finally_exit) exit_shell(); } bool input_is_interactive_terminal(const parseparam_T *pinfo) { if (!pinfo->interactive) return false; struct input_interactive_info_T *ir = pinfo->inputinfo; return isatty(ir->fileinfo->fd); } /********** Built-ins **********/ /* Options for the "exit" and "suspend" built-ins. */ const struct xgetopt_T force_help_options[] = { { L'f', L"force", OPTARG_NONE, true, NULL, }, #if YASH_ENABLE_HELP { L'-', L"help", OPTARG_NONE, false, NULL, }, #endif { L'\0', NULL, 0, false, NULL, }, }; /* The "exit" built-in. * If the shell is interactive, there are stopped jobs and the -f flag is not * specified, then prints a warning message and does not exit. */ int exit_builtin(int argc, void **argv) { const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, force_help_options, 0)) != NULL) { switch (opt->shortopt) { case L'f': forceexit = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return special_builtin_error(Exit_ERROR); } } if (!validate_operand_count(argc - xoptind, 0, 1)) return special_builtin_error(Exit_ERROR); size_t sjc; if (is_interactive_now && !forceexit && (sjc = stopped_job_count()) > 0) { fprintf(stderr, ngt("You have a stopped job!", "You have %zu stopped jobs!", sjc), sjc); fprintf(stderr, gt(" Use `exit' again to exit anyway.\n")); forceexit = nextforceexit = true; return Exit_FAILURE; } int status; const wchar_t *statusstr = ARGV(xoptind); if (statusstr != NULL) { if (!xwcstoi(statusstr, 10, &status) || status < 0) { xerror(0, Ngt("`%ls' is not a valid integer"), statusstr); status = Exit_ERROR; special_builtin_error(status); } } else { status = -1; } exit_shell_with_status(status); assert(false); } #if YASH_ENABLE_HELP const char exit_help[] = Ngt( "exit the shell" ); const char exit_syntax[] = Ngt( "\texit [-f] [exit_status]\n" ); #endif /* The "suspend" built-in, which accepts the following options: * -f: suspend even if it may cause a deadlock. */ int suspend_builtin(int argc, void **argv) { bool force = false; const struct xgetopt_T *opt; xoptind = 0; while ((opt = xgetopt(argv, force_help_options, 0)) != NULL) { switch (opt->shortopt) { case L'f': force = true; break; #if YASH_ENABLE_HELP case L'-': return print_builtin_help(ARGV(0)); #endif default: return Exit_ERROR; } } if (argc != xoptind) return too_many_operands_error(0); if (!force && is_interactive_now && getsid(0) == shell_pgid) { xerror(0, Ngt("refusing to suspend because of a possible deadlock.\n" "Use the -f option to suspend anyway.")); return Exit_FAILURE; } stop_myself(); if (doing_job_control_now) ensure_foreground(); return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE; } #if YASH_ENABLE_HELP const char suspend_help[] = Ngt( "suspend the shell" ); const char suspend_syntax[] = Ngt( "\tsuspend [-f]\n" ); #endif /* vim: set ts=8 sts=4 sw=4 noet tw=80: */ yash-2.49/yash.h000066400000000000000000000042531354143602500135400ustar00rootroot00000000000000/* Yash: yet another shell */ /* yash.h: basic functions of the shell and miscellanies */ /* (C) 2007-2013 magicant */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef YASH_YASH_H #define YASH_YASH_H #include #include #include "xgetopt.h" extern pid_t shell_pid, shell_pgid; extern _Bool shell_initialized; static inline void exit_shell(void) __attribute__((noreturn)); extern void exit_shell_with_status(int status) __attribute__((noreturn)); extern struct input_file_info_T *stdin_input_file_info; extern void exec_wcs(const wchar_t *code, const char *name, _Bool finally_exit) __attribute__((nonnull(1))); typedef enum exec_input_options_T { XIO_INTERACTIVE = 1 << 0, XIO_SUBST_ALIAS = 1 << 1, XIO_FINALLY_EXIT = 1 << 2, } exec_input_options_T; extern void exec_input(int fd, const char *name, exec_input_options_T options); extern _Bool nextforceexit; extern const struct xgetopt_T force_help_options[]; extern int exit_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char exit_help[], exit_syntax[]; #endif extern int suspend_builtin(int argc, void **argv) __attribute__((nonnull)); #if YASH_ENABLE_HELP extern const char suspend_help[], suspend_syntax[]; #endif /* Exits the shell with the last exit status. * This function executes the EXIT trap. * This function never returns. * This function is reentrant and exits immediately if reentered. */ void exit_shell(void) { exit_shell_with_status(-1); } #endif /* YASH_YASH_H */ /* vim: set ts=8 sts=4 sw=4 noet tw=80: */