📜 ⬆️ ⬇️

Convenient work in the console, or paint STDERR red

Work in the console


Many of us use the console every day, and, probably, everyone asked themselves the question: how can I do my work in the console more efficiently? What can I do to spend less time on routine tasks? In this article I would like to briefly talk about a few simple but useful things when working with bash, which you might not know about.

Reduce the number of letters typed


Aliases

One of the most useful things that all modern shells support is aliases. Aliases allow you to write fewer letters when typing commands. For example:

$ git status # On branch master nothing to commit, working directory clean $ alias st='git status' $ st # On branch master nothing to commit, working directory clean 

As you can see, the syntax for declaring aliases is very simple, so almost everyone uses aliases anyway. One of the drawbacks of using aliases is that the auto-complement for commands often stops working.

Example: (<TAB> is pressing Tab):
')
 $ git checkout <TAB><TAB> HEAD master origin/HEAD origin/master 

If we declare an alias co = 'git checkout', then pressing Tab stops working the way we expect and starts simply substituting file names (at least for bash):

 $ alias co='git checkout' $ co <TAB><TAB> Display all 124 possibilities? (y or n) .git/ MANIFEST.doc array.c bashline.c ... 


Thus, if you want to write fewer letters, then you have to abandon the auto-add-on ... Or not? Let's google a little bit and find this interesting function:

 function make-completion-wrapper () { local function_name="$2" local arg_count=$(($#-3)) local comp_function_name="$1" shift 2 local function=" function $function_name { ((COMP_CWORD+=$arg_count)) COMP_WORDS=( "$@" \${COMP_WORDS[@]:1} ) "$comp_function_name" return 0 }" eval "$function" } 


This interesting feature allows us to revert autocompletion for “complex” commands (that is, when we make an alias consisting of a command and additional arguments, as is the case with git checkout). It allows us to create a function that, in turn, can be used to wrap auto-add-in functions so that the auto-add-on for aliases continues to work. Sounds hard ..? The way it is :). Let's take a look at the usage example:

 $ make-completion-wrapper _git _co git checkout $ complete -o bashdefault -o default -o nospace -F _co co $ co <TAB><TAB> HEAD master origin/HEAD origin/master 

Let's analyze a little more:

1) make-completion-wrapper: Auto-completion functions for commands usually begin with an underline, and for example for the git command, this function is called "_git". With our great make-completion-wrapper function, we will create a new function called "_co" (for the co command), which is an alias for "git checkout"
2) complete: Register our new function "_co" as a handler for auto-add for the "co" command
3) Works!

To prevent aliases and auto-add-in commands from being lost, we will save these commands in ".bashrc" (or ".bash_profile" or ".profile") in your home directory, and we will be happy :).

Functions

The bash alias capabilities are limited, so sometimes it is useful to write functions (as in the previous example). Functions in bash work as if they were a separate command, but the functions are executed in the same context as the current shell. That is, the arguments to the functions are passed as "$ 1", "$ 2", etc., as if you were writing a shell script. The abbreviation "$ @" (in quotes) also works, which substitutes all its arguments "as is" in the right place. On serverfault you can find an example of such a function that colors stderr with a given command in red:

 $ color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1 $ color ls nonexistent ls: nonexistent: No such file or directory #    

Even if you do not write commands such as the one above, it is convenient to use functions, if you need to, say, always add the necessary arguments to the end of a command:

 $ function echoworld () { echo "$@" world; } $ echoworld Hello Hello world 

Or if you need to do some simple operations on the arguments:

 $ function gmo () { git merge "origin/$1"; } $ gmo master # git merge "origin/master" Already up-to-date. 

Getting rid of SSH input lags with mosh


If you often have to work on SSH with heavily remote servers (for example, the Amazon cloud in America), or you work on SSH via the mobile Internet, you know the problem of input delay. That is, you enter some character, and it appears on the remote side only after the round-trip interval, which can easily be 200 ms or more. In the case of the mobile Internet, input delays are felt much more strongly and it becomes uncomfortable to work.

Probably, the authors of the utility called mosh ( http://mosh.mit.edu ) are so fed up with the problem that they decided to write their own SSH replacement running on UDP and solving many SSH problems, for example, noticeable input delays and no feedback connections (write failed: broken pipe, which only appears when you try to enter something).

This utility also has one major drawback - there is currently no support for viewing history. That is, if you make cat from a large file or ls from a large directory, then most likely you will only get the last lines of the output, and the beginning will be “lost”. The authors themselves currently recommend using screen on the remote side to solve this problem, and in version 1.3 they promise to build similar functionality directly into the server (and client).

Bash patch to stderr red


In fact, patching bash to get a red stderr is not at all necessary, but it's interesting! There are already ready-made solutions that are easy to use, such as this: github.com/sickill/stderred . The project is a shared library that intercepts calls to write (2, ...) and fprintf from libc, and adds a wrapper around the necessary esc sequences to get the red color around.

So, we understood that other solutions exist, and they even suit everyone, so let's write our own anyway :)! I want to immediately say a separate thank you ezh for the assistance in the implementation of the patch.

1. Downloading the bash source ( ftp.gnu.org/gnu/bash )
2. Add them to git (git init && git add -A && git commit -m 'Initial commit')
3. Build bash (./configure && make)
4. Run bash and make sure everything works (./bash -l)
5. We start to understand the source code:

Find the shell.c file and see where bash initialization begins:

 #if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { 


After about 400 lines, while still in the main () function, at the very end we find the reader_loop () call:

 #if !defined (ONESHOT) read_and_execute: #endif /* !ONESHOT */ shell_initialized = 1; /* Read commands until exit condition. */ reader_loop (); exit_shell (last_command_exit_value); } 


It would be logical to wedge right before bash starts reading user input and somehow intercepts descriptor number 2 (stderr) if the necessary environment variable is set:

  shell_initialized = 1; color_stderr = get_string_value("COLOR_STDERR"); if (color_stderr && color_stderr[0]) { init_color_stderr(); } /* Read commands until exit condition. */ reader_loop (); 


How to intercept descriptor number 2? The obvious solution is to create a pipe, and replace the handle with the number 2 with our pipe, and in another thread read from there, and add the necessary esc sequences:

 static void *colorize_stderr(void *void_thread_args) { struct stderr_thread_data* data = (struct stderr_thread_data*)void_thread_args; int n; char buf[1024]; #define STDERR_PREFIX "\033[31m" #define STDERR_SUFFIX "\033[m" for (;;) { n = read(data->pipe, buf, sizeof(buf)); if (n <= 0) { if (errno == EINTR) continue; pthread_exit(NULL); } write(data->err, STDERR_PREFIX, sizeof(STDERR_PREFIX) - 1); write(data->err, buf, (size_t) n); write(data->err, STDERR_SUFFIX, sizeof(STDERR_SUFFIX) - 1); } } static void init_color_stderr () { pthread_t thr; int pipes[2]; static struct stderr_thread_data data; pipe(pipes); data.err = dup(2); dup2(pipes[1], 2); data.pipe = pipes[0]; pthread_create(&thr, NULL, colorize_stderr, (void*) &data); } 


However, the entire user input also becomes red: (Apparently, the readline library displays our input on the screen just in stderr ... After digging into the readline library, we insert into the file lib / readline / display.c in the rl_redisplay function (the function, by the way, is only 1300 lines) the following:

 /* Basic redisplay algorithm. */ void rl_redisplay () { /* ...   ... */ if (_rl_echoing_p == 0) return; _rl_output_some_chars("\033[m", 3); /*  -:  ,      ,    */ /* Block keyboard interrupts because this function manipulates global data structures. */ _rl_block_sigint (); RL_SETSTATE (RL_STATE_REDISPLAYING); 


If everything is done correctly and the necessary headers and method signatures are added (these actions were omitted for brevity), then when we write the line “export COLOR_STDERR = 1” in .bashrc and run the new compiled bash, the entire stderr will turn red, as in the screenshot at the beginning articles.

Since replacing the system bash is a bad idea, you can put a new, compiled bash, say, in ~ / bash and add the following to .bashrc:

 if [ ! -z "$PS1" ] && [ -z "$MY_BASH" ] && [ -x ~/bash ]; then export MY_BASH=1 exec ~/bash -l "$@" fi export COLOR_STDERR=1 


Upon login, it will be checked if "~ / bash" exists and is executable, and if so, it will replace the current process with "~ / bash -l" (that is, the login shell). The option COLOR_STDERR = 1 will color us stderr bash red.

The patched version of bash is posted on github: github.com/YuriyNasretdinov/bash

Since the edits are designed as a “crutch” to bash, it is unlikely that this patch will be accepted into the main branch, but the implementation itself seems to me rather amusing: to work, in fact, you need to set only one environment variable (you can easily cut it from the code, and then this mode will be enabled by default), and everything else will work without changes.

I hope, dear reader, you could learn something useful from this article :). Happy New Year!

Source: https://habr.com/ru/post/207768/


All Articles