Bash undeclaring a readonly variable (declare -r var) - zsh

I can declare a bash variable as read only:
var=myname
declare -r var
and then when I try to change the value :
var=anothername
I get (in zsh for example)
>>read-only variable: var
How can I "undeclare" or unset this variable?

I found this here unset:
$ cat << EOF| sudo gdb
attach $$
call unbind_variable("var")
detach
EOF

You can't in bash, readonly means you can't change that variable in anyway, including getting rid of it.

Related

Z autocompletion cp target directory

It has been a while since I started using the z command of zsh: its frecency-based features are very useful! I am now looking for a similar auto-completion in order to complete the target when using cp:
Expected: behavior:
$: ls
-> foo.txt bar.txt
$: cp + TAB
-> suggests iteratively foo.txt and bar.txt
$: cp foo.txt rece + TAB
-> suggest /home/user/Documents/recent_directory
Is there any way doing so? Thanks for any help!
Yes, there is: Just install zsh-autocomplete.
(Disclaimer: I'm the maintainer of zsh-autocomplete.)
An alternative approach is to use Dynamic named directories http://zsh.sourceforge.net/Doc/Release/Expansion.html#Filename-Expansion .
Suppose you have a function ffz-get that uses fzf to select the recent directory. Then you can:
function zsh_directory_name_1() {
local type="$1" arg="$2"
if [[ "$type" == n ]] ; then
# Dynamic named directories
# e.g., `ll ~[dl]`
o="$(ffz-get "$arg")"
typeset -ga reply
reply=("$o")
return 0
elif [[ "$type" == c ]]; then
return
elif [[ "$type" == d ]]; then
return 1
else
echo "$0: Unknown type '$type'"
return 1
fi
return 1
}
typeset -ag zsh_directory_name_functions=()
zsh_directory_name_functions+='zsh_directory_name_1'
You can see my own implementation of ffz-get at https://github.com/NightMachinary/.shells/blob/master/scripts/zshlang/auto-load/others/fuzzy/z.zsh , but you would need to adapt it to use it without using all of my dotfiles.
This whole thing allows you to do your example like so:
cp foo.txt ~[rece
# typing the closing ']' will open `fzf` so that you select the candidate you want
cp foo.txt ~[rece]/bar.txt
Note that my own ffz-get caches user choices for each query (in redis), and I am not sure if this is necessary or not.
I ended up using the following:
$: cp foo.txt $(j rece)
-> copy ./foo.txt to /home/user/Documents/recent_directory
with j alias for autojump. I assume it would still work for z

How to catch "$variable is not defined" in jq?

Let's pretend I'm running something like this:
jq -nr --arg target /tmp \
'(["echo","Hello, world"]|#sh)+">\($target)/sample.txt"' \
| sh
Everything is fine unless I forgot to pass variable $target:
$ jq -nr '(["echo","Hello, world"]|#sh)+">\($target)/sample.txt"'
jq: error: $target is not defined at <top-level>, line 1:
(["echo","Hello, world"]|#sh)+">\($target)/sample.txt"
jq: 1 compile error
How can I catch this and use default value?
I've tried:
$target?
($target)?
try $target catch null
$target? // null
But it seems to be parsing-time error, which obviously can't be caught at runtime. Have I've missed any dynamic syntax?
I've found that command-line arguments can be found in $ARGS.name, but there are two drawbacks:
This was introduced in version 1.6, but I have 1.5 on CentOS 7.
It doesn't catch locally defined variables.
Assuming you need to do something more useful with jq than write 'Hello World' over a text file. I propose the following,
Maybe we can learn some programming tips from Jesus:
"Give to Caesar what belongs to Caesar, and give to God what belongs to God"
Suppose that Caesar is bash shell and God is jq, bash is appropriate to work and test the existence of files, directories and environment variables, jq is appropriate to process information in json format.
#!/bin/bash
dest_folder=$1
#if param1 is not given, then the default is /tmp:
if [ -z $dest_folder ]; then dest_folder=/tmp ; fi
echo destination folder: $dest_folder
#check if destination folder exists
if [ ! -d $dest_folder ]
then
echo "_err_ folder not found"
exit 1
fi
jq -nr --arg target $dest_folder '(["echo","Hello, world"]|#sh)+">\($target)/sample.txt"' | sh
#if the file is succesfully created, return 0, if not return 1
if [ -e "$dest_folder/sample.txt" ]
then
echo "_suc_ file was created ok"
exit 0
else
echo "_err_ when creating file"
exit 1
fi
Now you can include this script as a step in a more complex batch, because it is congruent with linux style, returning 0 on success.

How do you pass arguments to custom zsh functions?

How do you pass arguments to custom zsh functions?
For instance:
function kill_port_proc(port) {
lsof -i tcp:<port interpolated here>| grep LISTEN | awk '{print $2}'
}
I'm seeing so many examples online with ZSH functions, but there barely anything on passing arguments and interpolating them.
When defining a function, you cannot specify required arguments. That's why using both the function keyword and parens () seems useless to me.
To get the passed arguments, use positional parameters.
The positional parameters provide access to the command-line arguments of a shell function, shell script, or the shell itself; [...]
The parameter n, where n is a number, is the nth positional parameter. The parameter $0 is a special case [...]
About the $0 positional parameter:
The name used to invoke the current shell, or as set by the -c command line option upon invocation.
If the FUNCTION_ARGZERO option is set, $0 is set upon entry to a shell function to the name of the function, and upon entry to a sourced script to the name of the script, and reset to its previous value when the function or script returns.
Using your example:
function kill_port_proc {
lsof -i tcp:"$1" | grep LISTEN | awk '{print $2}'
}
Personaly, I like to document the function by, at least, adding the function signature prior to the definition.
Then, I declare local parameters for each arguments and readonly parameters when I want to protect them from unexpected modification.
If the argument is mandatory, I use a special parameter expansion form:
${name?word}
${name:?word}
In the first form, if name is set, or in the second form if name is both set and non-null, then substitute its value;
otherwise, print word and exit from the shell. Interactive shells instead return to the prompt.
If word is omitted, then a standard message is printed.
How I would write your example:
# kill_port_proc <port>
function kill_port_proc {
readonly port=${1:?"The port must be specified."}
lsof -i tcp:"$port" | grep LISTEN | awk '{print $2}'
}
my_function() {
if [ $# -lt 2 ]
then
echo "Usage: $funcstack[1] <first-argument> <second-argument>"
return
fi
echo "First argument: $1"
echo "Second argument: $2"
}
Usage
$ my_function
Usage: my_function <first-argument> <second-argument>
$ my_function foo
Usage: my_function <first-argument> <second-argument>
$ my_function foo bar
First argument: foo
Second argument: bar

Loop over environment variables in POSIX sh

I need to loop over environment variables and get their names and values in POSIX sh (not bash). This is what I have so far.
#!/usr/bin/env sh
# Loop over each line from the env command
while read -r line; do
# Get the string before = (the var name)
name="${line%=*}"
eval value="\$$name"
echo "name: ${name}, value: ${value}"
done <<EOF
$(env)
EOF
It works most of the time, except when an environment variable contains a newline. I need it to work in that case.
I am aware of the -0 flag for env that separates variables with nul instead of newlines, but if I use that flag, how do I loop over each variable? Edit: #chepner pointed out that POSIX env doesn't support -0, so that's out.
Any solution that uses portable linux utilities is good as long as it works in POSIX sh.
There is no way to parse the output of env with complete confidence; consider this output:
bar=3
baz=9
I can produce that with two different environments:
$ env -i "bar=3" "baz=9"
bar=3
baz=9
$ env -i "bar=3
> baz=9"
bar=3
baz=9
Is that two environment variables, bar and baz, with simple numeric values, or is it one variable bar with the value $'3\nbaz=9' (to use bash's ANSI quoting style)?
You can safely access the environment with POSIX awk, however, using the ENVIRON array. For example:
awk 'END { for (name in ENVIRON) {
print "Name is "name;
print "Value is "ENVIRON[name];
}
}' < /dev/null
With this command, you can distinguish between the two environments mentioned above.
$ env -i "bar=3" "baz=9" awk 'END { for (name in ENVIRON) { print "Name is "name; print "Value is "ENVIRON[name]; }}' < /dev/null
Name is baz
Value is 9
Name is bar
Value is 3
$ env -i "bar=3
> baz=9" awk 'END { for (name in ENVIRON) { print "Name is "name; print "Value is "ENVIRON[name]; }}' < /dev/null
Name is bar
Value is 3
baz=9
Maybe this would work?
#!/usr/bin/env sh
env | while IFS= read -r line
do
name="${line%%=*}"
indirect_presence="$(eval echo "\${$name+x}")"
[ -z "$name" ] || [ -z "$indirect_presence" ] || echo "name:$name, value:$(eval echo "\$$name")"
done
It is not bullet-proof, as if the value of a variable with a newline happens to have a line beginning that looks like an assignment, it could be somewhat confused.
The expansion uses %% to remove the longest match, so if a line contains several = signs, they should all be removed to leave only the variable name from the beginning of the line.
Here an example based on the awk approach:
#!/bin/sh
for NAME in $(awk "END { for (name in ENVIRON) { print name; }}" < /dev/null)
do
VAL="$(awk "END { printf ENVIRON[\"$NAME\"]; }" < /dev/null)"
echo "$NAME=$VAL"
done

How to set font color for STDOUT and STDERR

I want to differentiate the STDOUT and STDERR messages in my terminal.
If a script or command is printing a message in terminal I want to differentiate by colors; is it possible?
(E.g. stderr font color is red, and stdout font color is blue.)
Example (using bold):
$date
Wed Jul 27 12:36:50 IST 2011
$datee
bash: datee: command not found
$alias ls
alias ls='ls --color=auto -F'
$aliass ls
bash: aliass: command not found
Create a function in a bash shell or script:
color()(set -o pipefail;"$#" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1
Use it like this:
$ color command -program -args
It will show the command's stderr in red.
Keep reading for an explanation of how it works. There are some interesting features demonstrated by this command.
color()... — Creates a bash function called color.
set -o pipefail — This is a shell option that preserves the error return code of a command whose output is piped into another command. This is done in a subshell, which is created by the parentheses, so as not to change the pipefail option in the outer shell.
"$#" — Executes the arguments to the function as a new command. "$#" is equivalent to "$1" "$2" ...
2>&1 — Redirects the stderr of the command to stdout so that it becomes sed's stdin.
>&3 — Shorthand for 1>&3, this redirects stdout to a new temporary file descriptor 3. 3 gets routed back into stdout later.
sed ... — Because of the redirects above, sed's stdin is the stderr of the executed command. Its function is to surround each line with color codes.
$'...' A bash construct that causes it to understand backslash-escaped characters
.* — Matches the entire line.
\e[31m — The ANSI escape sequence that causes the following characters to be red
& — The sed replace character that expands to the entire matched string (the entire line in this case).
\e[m — The ANSI escape sequence that resets the color.
>&2 — Shorthand for 1>&2, this redirects sed's stdout to stderr.
3>&1 — Redirects the temporary file descriptor 3 back into stdout.
Here's a hack that I thought of and it seems to work:
Given the following aliases for readability:
alias blue='echo -en "\033[36m"'
alias red='echo -en "\033[31m"'
alias formatOutput='while read line; do blue; echo $line; red; done'
Now, you need to first set the font color in your terminal to red (as the default, which will be used for stderr).
Then, run your command and pipe the stdout through formatOutput defined above (which simply prints each line as blue and then resets the font color to red):
shell$ red
shell$ ls / somenonexistingfile | formatOutput
The above command will print in both stderr and stdout and you'll see that the lines are coloured differently.
Hope this helps
UPDATE:
To make this reusable, I've put it all in a small script:
$ cat bin/run
#!/bin/bash
echo -en "\033[31m" ## red
eval $* | while read line; do
echo -en "\033[36m" ## blue
echo $line
echo -en "\033[31m" ## red
done
echo -en "\033[0m" ## reset color
Now you can use this with any command:
$ run yourCommand
I color stderr red by linking the file descriptor to a custom function that adds color to everything that goes through it. Add to following to your .bashrc:
export COLOR_RED="$(tput setaf 1)"
export COLOR_RESET="$(tput sgr0)"
exec 9>&2
exec 8> >(
perl -e '$|=1; while(sysread STDIN,$a,9999) {print
"$ENV{COLOR_RED}$a$ENV{COLOR_RESET}"}'
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; }
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'
So what is happening? The debug trap is executed just before and immediately after executing a command. stderr is thus redirected before a command is executed to enable red output. PROMPT_COMMAND is evaluated before the prompt is shown and with this I restore stderr to its normal state. This is necessary because PS1 and PS2 (your prompt) are printed over stderr and I do not want a red prompt. voila, red output over stderr!
You should check out stderred: https://github.com/sickill/stderred
Yes it's not possible natively. You'll have to hack the tty management (in the kernel).
I somehow finished some little C wrapper before I saw the other answers :-)
Might be buggy, and values are hardcoded, don't use this except for testing.
#include "unistd.h"
#include "stdio.h"
#include <sys/select.h>
int main(int argc, char **argv)
{
char buf[1024];
int pout[2], perr[2];
pipe(pout); pipe(perr);
if (fork()!=0)
{
close(1); close(2);
dup2(pout[1],1); dup2(perr[1],2);
close(pout[1]); close(perr[1]);
execvp(argv[1], argv+1);
fprintf(stderr,"exec failed\n");
return 0;
}
close(pout[1]); close(perr[1]);
while (1)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(pout[0], &fds);
FD_SET(perr[0], &fds);
int max = pout[0] > perr[0] ? pout[0] : perr[0];
int v = select(max+1, &fds, NULL, NULL, NULL);
if (FD_ISSET(pout[0], &fds))
{
int r;
r = read(pout[0], buf, 1024);
if (!r) {close(pout[0]); continue;}
write(1, "\033[33m", 5);
write(1, buf, r);
write(1, "\033[0m", 4);
}
if (FD_ISSET(perr[0], &fds))
{
int r;
r = read(perr[0], buf, 1024);
if (!r) {close(perr[0]); continue;}
write(2, "\033[31m", 5);
write(2, buf, r);
write(2, "\033[0m", 4);
}
if (v <= 0) break;
}
return 0;
}
Edit: Compared to the shell solution, this one will preserve the order of lines/characters more often. (It's not possible to be as accurate as direct tty reading.) Hitting ^C won't show an ugly error message, and it behaves correctly on this example:
./c_color_script sh -c "while true; do (echo -n a; echo -n b 1>&2) done"
I'm surprised that nobody has actually figured out how to color stdio streams. This will color stderr red for the entire (sub)shell:
exec 3>&2
exec 2> >(sed -u 's/^\(.*\)$/'$'\e''[31m\1'$'\e''[m/' >&3)
In this case, &3 will hold the original stderr stream.
You should not be passing any commands to exec, only the redirects. This special case causes exec to replace the current (sub)shell's stdio streams with those that it receives.
There are a few caveats:
Since sed will be running persistently in a parallel subshell, any direct output immediately following a write to the colored stdio will probably beat sed to the tty.
This method uses a FIFO file descriptor; FIFO nodes only deal in lines. If you don't write a linefeed to the stream, your output will be buffered until a newline is encountered. This is not buffering on sed's part: it's how these file types function.
The most troublesome of the caveats is the first, but a race condition can be more or less avoided by applying similar processing to all outputs, even if you use the default color.
You can perform similar processing for single commands by piping to the same sed command with the normal pipe operator (|). Piped chains are executed synchronously, so no race condition will occur, though the last command in a pipe chain receives its own subshell by default.
Expanding on the answer #gospes gave, I added the functionality to print out partial lines without waiting for a newline, and some comments. Allows for better output from wget or typing in a interactive shell.
exec 9>&2
exec 8> >(
while [ "$r" != "1" ]; do
# read input, no field separators or backslash escaping, 1/20th second timeout
IFS='' read -rt 0.05 line
r=$?
# if we have input, print the color change control char and what input we have
if ! [ "${#line}" = "0" ]; then
echo -ne "\e[1;33m${line}"
fi
# end of line detected, print default color control char and newline
if [ "$r" = "0" ] ; then
echo -e "\e[0m"
fi
# slow infinite loops on unexpected returns - shouldn't happen
if ! [ "$r" = "0" ] && ! [ "$r" = "142" ]; then
sleep 0.05
fi
done
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; }
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'
I used bold yellow (1;33) but you can replace it with whatever, red for example (31) or bold red (1;33), and I arbitrarily chose 0.05 seconds for re-checking for end-of-lines and pausing on unexpected return codes (never found any); it could probably be lowered, or possibly removed from the read command.
You can make use of grep for this. Note that this assumes that grep is configured to have coloured output (this is the default on many systems).
$ aliass ls 2> >(GREP_COLORS='ms=01;31' grep .) 1> >(GREP_COLORS='ms=01;32' grep .)
aliass: command not found
This is a little long winded, if you are simply wanting to distinguish stderr fromstdout you can simply do this:
$ (echo "this is stdout"; echo "this is stderr" >&2) | grep .
this is stderr
this is stdout
This will result in stdout being highlighted with the default grep colour and stderr being white.
This might be the opposite of what you want if your default grep colour is red. If so you can explicitly set the grep colour to green:
$ GREP_COLORS='ms=01;32'
$ (echo "this is stdout"; echo "this is stderr" >&2) | grep .
If you explicitly want to get red output for stderr:
$ GREP_COLORS='ms=01;31'
$ (echo "this is stdout"; echo "this is stderr" >&2) 2> >(grep .)
Note that this solution will not preserve the output order.

Resources