Search This Blog

Showing posts with label zsh. Show all posts
Showing posts with label zsh. Show all posts

Friday, May 01, 2026

Watching GitHub PRs

In one of my previous posts I explained how to check the status of your PRs from the command line. Recently, I added one more improvement to ensure that also PRs without any reviews yet are listed with the PENDING state. This is achieved by using the fallback operator // in the jq\(.reviews[-1].state // "PENDING") command.


gh pr list \
 --author @me \
 --json "number,title,reviews" \
| jq '.[] | "PR \(.number): \(.title) is \(.reviews[-1].state // "PENDING")"'

I also discovered (with some help from AI) how to watch the PR state live in full colour: combine the watch tool  --color option with one of 2 environment variables:


watch --color 'GH_FORCE_TTY=1 gh pr view'
watch --color 'CLICOLOR_FORCE=1 gh pr view'
  • GH_FORCE_TTY=1: is the gh specific option and forces the GitHub CLI client to behave as it is running in a terminal, even if it's not (e.g. piped or run inside watch).
  • CLICOLOR_FORCE=1: is part of the CLICOLOR standard and affects other tools as well. It forces the tool to output colours regardless whether it is running in a terminal or not. 

Links

Monday, March 30, 2026

More jq from journalctl

I've found another use case for `jq` when parsing service logs stored with journald. This time, I want to extract all non-INFO level logs from the service called slinky. In the previous example, I used awk to print only the part of a line that is valid JSON. However, sed might be better suited for this task. The following rule removes (replaces with an empty string) the beginning of the line up to the colon followed by a space ": ", which separates the timestamp from the log entry (JSON):

's/^.\+\]:\ //'

Examples

There are two examples of the command pipelines below. 

The first one checks the logs from the last 2 hours:


journalctl --since "2 hours ago" -u slinky.service\
 | sed -e 's/^.\+\]:\ //'\
 | jq 'select(.level != "info") '

The second one continuously prints new entries:


journalctl -f -u slinky.service\
 | stdbuf -oL sed -e 's/^.\+\]:\ //'\
 | jq 'select(.level != "info") '

The journalctl command is nearly identical in both examples (--since vs. -f). The jq select statement and the sed string replacement are the same. The main difference is that the latter uses the stdbuf command. It allows running the following command with modified buffering. The -oL option means that the standard output of the sed command is flushed line by line, enabling each entry to be passed immediately to jq.

Monday, March 02, 2026

Download GitHub Actions logs

I've been using GitHub CLI more and more lately. Recently, I had to debug a failing of GitHub Action run. Browsing long logs in the WebUI is a bit clunky, so I started downloading the full logs via the CLI. 

It is straightforward `gh` command if you already know the run number --  and that information can be obtained from the `gh` command with different options. 

I end up with following two-part shell snippet: 

VIEW=$(\
 gh run list \
 | grep $(git branch --show-current) \
 | head -1\
 | awk '{print $(NF-2)}') \
&& \
gh run view ${VIEW} --log > ~/Downloads/${VIEW}.log
 

The first command assign the run number to the VIEW variable. It parses the output of the `gh run list` command by:

  • filtering run for the current git branch (`grep`)
  • taking the most recent one (`head`)
  • extracting the run number (third column from the end via `awk`).

The VIEW variable is then used to fetch the logs for the specific run and save them to the uniquely named file in the Downloads folder.

Assumptions:

  • the workflow run belongs to the current git branch
  • it's the latest run for the branch 
  • The Downloads folder doesn't already contain a file with the same number (it would be overwritten)

Tuesday, January 06, 2026

Hash of the latest git commit

From time to time I have, not only know, but to paste the hash of the latest git commit somewhere else. I prepared a one-liner to get the hash in a format good to copy & paste:
git log --pretty=oneline |\
 awk '{print $1}'

Wednesday, November 26, 2025

Check status of your GitHub PR from CLI

This github CLI command (`gh`) will list each PR authored by you. The output contains the PR number, title and all reviews. They are printed as json, and piped to `jq`. From the output, jq filters out the id, title and state of each review and interpolate it into a string.

gh pr list \
 --author @me \
 --json "number,title,reviews"\
 | jq \
 '.[] | "PR \(.number): \(.title) is \(.reviews[].state)"'

Links

Monday, November 03, 2025

IP address oneliner

Command

The command to get a public IP from UNIX `ip addr` command:

ip -4 -o addr| \
 awk -F "( |/)" '$0 !~ " (10|127|172|192)\\." {print $7}'

The `ip` command options makes it to print only IPv4 addresses (`-4`) and output them in one line each (`-o`).

The `awk` app uses space and `/` as Field separators (`-F "( |/)`) and looks for lines without a substring containing one of 10, 127, 172, 192 numbers followed by a dot and preceded by space (`$0 !~ " (10|127|172|192)\\."`. It prints a seventh filed (`{print $7}`).

Example of ip command output

To better understand the pattern used in the `awk` below is an example of the output of the `ip` command.

1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
2: enp2s0f0np0    inet 56.112.121.59/32 metric 100 scope global dynamic enp2s0f0np0\       valid_lft 64241sec preferred_lft 64241sec
4: docker0    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0\       valid_lft forever preferred_lft forever

Thursday, August 28, 2025

Check the time of the current block in a Cosmos blockchain

echo BLOCK: $(curl -s 127.0.0.1:26657/status |\
 jq -r '.result.sync_info.latest_block_time');\
echo "NOW:   $(LC_TIME=C date -u +%Y-%m-%dT%X.%NZ)" 

Blockchain perspective 

If you deal with a Cosmos based blockchain, this command (actually there are 2 commands) helps to visualise the difference between the time of the latest block and current time. That's helpful if a node needs to catch up (e.g. after restarting from a snapshot). 

UNIX perspective 

The command has a few interesting "UNIX features". First, the curl from the localhost is silent (-s option) to avoid showing download stats. Its output is redirected to jq, which presents the raw (-r option) the time of latest block (.result.sync_info.lastest_block_time field). 

The second command returns the current time. The LC_TIME=Csets the locale to a standard, non-localized format, ensuring the output is always in a 24-hour clock and not a 12-hour AM/PM format, which can vary by location.  The option -u forces the output in the UTC, rather than the local time. Finally, +%Y-%m-%dT%X.%NZ formats date identical like the cosmos endpoint. 

Tuesday, July 15, 2025

Take me to the git home (and checkout me back)

I'm moving quite a lot around git projects. For some time, I have been wondering if there is a way to have an equivalent of argumentless `cd`. But rather of taking you back $HOME, changing directory to the git top-level one. I decided to make a deeper dive into the problem and ended up with the following function in zsh:

  function cdt ()
   cd $(git rev-parse --show-toplevel)}

Originally, I had an alias, but it did not work as designed. It took a root of git repository when sources (usually, during shell start). For a refernce that is my original alias.

  alias cdt="cd $(git rev-parse --show-toplevel)"

Another "dimension" of git project you can move around are branches. Last year, I learnt that the `-` works in the `git checkout` the same as in the `cd` command. So if we switch between branches, try:

  git checkout -

Sunday, February 04, 2024

Open file from command line (in Linux and Macos)

One of the nice feature of MacOS is the open command. It allows opening files directly from the command line without knowing the application linked to the file type. For example:

 open interesting.pdf 

opens the interesting.pdf file using whatever program is assigned to open PDF files. (If you want more example about the open command, you can check this link.)

For some time I wonder about a Linux equivalent. Recently decided to look for it more actively and check if AI might help. It did, and pointed at the gio command from the Gnome Input/Output library. After adding a function or an alias following block of code to the .zshrc, I have a Linux equivalent.

  •  alias

open="gio open"

  • function:

open () {

    gio open $1

}   

And finally, xdg-open is an alternative for the "gio open".

Wednesday, May 17, 2023

How to find s3 bucket in multiple accounts (with awk and multiple field separator)

Imagine you have quite a few AWS accounts. In one of them, you don't know which one, there is an S3 bucket. The AWS CLI with awk and zsh can help to find it.

In the first step, let's prepare a list of all accounts, or rather profiles from the AWS CLI config (the ~/.aws/config file).

accounts=($(awk -F "( |])" '/profile sso/ {print $2}'  ~/.aws/config))

In the example, we limit the list only to profiles with the prefix "sso". The command uses awk to find any line with the string "profile sso" and print the second field from it. However, it does no use the standard field separator. There are 3 characters working as a separator: space, "|" and "]". Please also note the awk command is two pairs of "()".

 The list is saved into the accounts variable and used in the second command. It lists all s3 buckets from each account, and grep for the selected string, which of course can be the whole bucket name.

p=sso-prod 
bucket=my-company-not-so-important-bucket
for accounts ($accounts) {echo $account; aws s3 ls --profile $p| grep $bucket}

Thursday, October 22, 2020

Kernel module info

A few commands to help in checking and adjusting kernel module options: 

  1. Display information about kernel module, including all options

    modinfo $KERNEL_MODULE

  2. Display information about current value for each option

    for i (/sys/module/$KERNEL_MODULE/parameters/*) {\
      echo $(basename $i);\
      cat $i\
    }


  3. Change kernel module option without restart (temporary)

    echo "$NEW_VALUE" > /sys/module/$KERNEL_MODULE/parameters/$OPTION

  4. Load module with an option set to value
    insmod $PATH_TO_MODULE/$KERNEL_MODULE.ko\ $OPTION1=$VALUE1 \
    $OPTION2=$VALUE2


  5. Ensure kernel module option is set during boot up with grub
    1. Open /etc/default/grub
    2. Add/edit following line
      GRUB_CMDLINE_LINUX='$KERNEL_MODULE.$OPTION=$VALUE'

Of course $KERNEL_MODULE, as well as all other strings with "$" at the beginning, has to be replaced by appropriate value.

In CRUX /etc/default/grub does not exist, so it has to be created.

 Useful links

Wednesday, August 26, 2020

How to find a word in files spread over many subdirectories

Imagine that you need to try to find a word in hundreds files located in a plenty of subfolders. grep -Rc might be a good candidate, but it's going to print the list of all files and the word is in just a handful of them. To make the output better grep can be joined with awk (and in the example below with zsh). 

I needed to find an example of sqlcmd command usage in my Ansible roles collection. I used the power of ZSH globbing (a few other examples can be found here and in other places) to limit search to yml file only. ZSH "**" pattern is also a nice replacement to the grep "--recursive"option. In awk the "-F" flag replaces the standard delimiter with the ":" character and prints only lines with second field not equal to zero.

grep -c sqlcmd **/*.yml | awk -F":" ' ($2 != 0 ){print $0}'

Sunday, May 19, 2019

Ansi control characters in Vim


The ANSI/VT100 terminals and their emulators allows to use escape sequences to  display colours and formatted text. (Check this link for more information on how to do this.) That's look cool in a terminal window. Problems starts when terminal output is redirected to a file (e.g. logs). Editors like Vim are virtual useless with such files, because files are full of "rubbish". At least by default, because for Vim there is a good plug-in called "Asci Highlighting". It takes escapes sequences and colour text appropriately.


Links:
  • https://misc.flogisoft.com/bash/tip_colors_and_formatting 
  • http://www.drchip.org/astronaut/vim/index.html#ANSIESC


Tuesday, April 30, 2019

Get Public IP address of Azure VM from shell on VM

Sometimes you need to get an IP address of the VM from inside of it. You can do this relatively simple from Azure VM with curl and jq thanks to Metadata endpoint as described in this document https://azure.microsoft.com/en-us/blog/announcing-general-availability-of-azure-instance-metadata-service/

And if you need the public IP address of interface eth0 this is the command:

curl -H Metadata:true http://169.254.169.254/metadata/instance?api-version=2017-04-02| jq '.network.interface[0].ipv4.ipAddress[0].publicIpAddress'


Tuesday, February 21, 2017

Firefox update in Crux

Updating Firefox, without rebuilding all other ports in Crux, is not the easiest task. Quite often you need to update one of packages Firefox depends one, but not all of them.

In my case, limited space on root partition is an additional problem. In the same time I have another, much bigger partition attached.

To address both issues I prepared this small script. It updates require dependencies (autoconf, sqlite, libpng, nspr, nss) and then updates Firefox, but with working directory in no default location (PKGMK_WORK_DIR).


prt-get update autoconf
prt-get update sqlite3
prt-get update libpng
prt-get update nspr
prt-get update nss
PKGMK_WORK_DIR=/media/pictures prt-get update firefox

Saturday, December 31, 2016

Control terminal name and comment block in VIM

Somewhere (I think it was Stackoverflow) I found simple command to control the name of terminal from commandline which works very nice with iterm2 tabs on MacOSX and decided to add to my zsh environment this function:

termname() {
 echo -en "\e]1; $1 \a"
}
 

And if we are saying about Stackoverflow one of the most useful Vim suggestion I've ever found is this instruction to comment/uncomment a multiple lines in VIM.

Tuesday, November 10, 2015

Many AWS accounts and Zsh

That might not be a common problem, but I have to deal with many AWS accounts in the same time. For example I might to have to run an Ansible playbook for one account and a CLI commend for other one in parallel. To make my life easier i wrote this ZSH function. (It's probably going to work in other advance shell).

There are two ways to use it.
  • awsenv list - returns the list of all available accounts/environments.
  • awsenv - sets, based on values provided in ~/.aws/credentaials and ~/.aws/config, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION.
The list is create with simple AWK script (assuming that any lines with "[]" is OK.

The actual command to set environment uses two AWK scripts. First one looks for requested account name and set variable "a" to 1. When "a" equals 1 it prints shell export command for access_key_id and for secret_access_key to  standard output, which is redirected to $TMP_FILE. Then it sources, prints and deletes that file.

Please note that in current form script requires access_key_id being define before secret_access_key.  Printing the value of all variables, especially secret_aceess_key could be consider as security weakness, so you might want to modify/remove "cat $TMP_FILE line.

# vim: set filetype=sh
awsenv () {
    if [[  $1 == list ]]
        then
        print $1
        awk  '/\[.*\]/ {print $1}'  ~/.aws/credentials
    else
        TMP_FILE=/tmp/current_aws
        awk \
           'BEGIN{a=0};\
           /\['$1'\]/ {a=1};\
           /access_key_id/ {if (a==1){printf "export %s=%s\n", toupper($1), $3}};\
           /secret_access_key/ {if (a==1) {printf "export %s=%s\n", toupper($1), $3;a=0}}'\
            ~/.aws/credentials > $TMP_FILE
        awk \
            'BEGIN{a=0};\
            /\[profile '$1'\]/ {a=1};\
            /region/ {if (a==1){printf "export AWS_DEFAULT_%s=%s\n", toupper($1), $3; a=0}}'\
            ~/.aws/config >> $TMP_FILE
        source $TMP_FILE
        cat $TMP_FILE
        rm $TMP_FILE
    fi
}



Finally, initial version of this script and some discusion on profiles in older boto versions is in this post http://larryn.blogspot.co.uk/2015/03/how-to-deal-with-aws-profiles.html

Saturday, May 02, 2015

zsh and ssh-agent

 INTRODUCTION

One of the post which gets some attention on this blog is My Way For Binding SSH Agent With Zshell. The method presented there is far from ideal and it stopped to work for me some time ago. After that I wrote a new version. I think it is much better and should work with bash or other shells. I tested it on Ubuntu and Crux.

ZSSH


I have the .zssh file in my home directory. It is sources by my .zshrc file. The .zssh consists of 3 functions.

SSHAGENT

The first function is responsible for starting ssh-agent.

sshagent () {
    SSHAGENT=$(ps ax|grep "[s]sh-agent"| grep -cv Z)
    if (( $SSHAGENT == 0 ))
    then
        sshupdate
    else
        SSHPID="$(ps -eo pid,command | awk '/ ssh-[a]gent/ {print $1}');"
        SSHPID_ENV=$(awk  '/Agent/ {print $NF}' ~/.ssh-env)
        if [[ $SSHPID == $SSHPID_ENV ]]
        then
            source ~/.ssh-env
        else
            killall ssh-agent
            sshupdate
        fi
    fi
}


It checks if a ssh-agent runs already and it isn't a zombie. (On one of my systems, after starting a desktop environment, I always had a zombie ssh-agent running.) If there is no ssh-agent running the function calls sshupdate, another function described below. If the agent is present and live in a system the function then compares ssh-agent pid with the information saved in the ~/.ssh-env file. (See sshupdate paragraph for more information.) If informations are consistence it sources .ssh-env.  If not it kills all ssh-agent and the calls sshupdate.

SSHUPDATE

This is a very simply function calling ssh-agent and saving its output to a file.

sshupdate () {
    ssh-agent > ~/.ssh-env
    source ~/.ssh-env
}


The output then can be sourced by other functions or processes. Oh, and if you don't remember/know the output of ssh-agent looks like that:

SSH_AUTH_SOCK=/tmp/ssh-BnXafqRnOSHx/agent.1884;
export SSH_AUTH_SOCK;
SSH_AGENT_PID=1885; 

export SSH_AGENT_PID;
echo Agent pid 1885;









SSHADD

Finally the function responsible for adding your ssh key.

sshadd () {
    if (( $(ssh-add -l | grep -c $USER) == 0 ))
    then
        ssh-add
    else
        ssh-add -l
    fi
}


It checks the number of added keys. If a key from you home directory, or having your username in the path, is not present it adds it. Otherwise it lists all added keys.

USAGE

sshagent is called from your .zshrc, so it should be present during every session. sshadd need to be called by you, when you need it first time.

FURTHER UPDATES

What if you have more than one key and you would like ti add all of them in the same time. Then you could try to use the 'file' program to find ssh keys in the.ssh, or other, directory and source all of them.


Tuesday, January 06, 2015

Count processes per state per application

In previous posts (here and here) I discussed how to count thread in a given state for a give process. Recently, I had another problem - I needed to count number of processes per application per state. My previous commands wouldn't work, so I wrote an alternative version.

while [ 1 ];
do
    date;
    cat /proc/loadavg;
    ps -Leo state,args |
     awk ' $1 ~ /(D|R)/ {state[$0]++} \
      END{ for (j in state) {printf "%s - %d\n", j, state[j]}}' |
      sort -k 2;
    echo "---";
    sleep 5;
done 

There is not PID and args are included in the output list as a whole. worried for number of processes.

One more thought. Dropping "$1 ~ /(D|R)/" can be useful in case of problem with total number of processes. But then the whole command should be a bit modified, so the results are sorted by number of processes. Simplified version would look like this one:

while [ 1 ];
do
    ps -Leo state,args |
     awk ' $1 ~ /(D|R)/ {state[$0]++} \
      END{ for (j in state) {printf "%d - %s\n", state[j], j}}' |
      sort -n;
    echo "---";
    sleep 5;
done 

Tuesday, February 04, 2014

shell, history and substitution

One of the most known tricks in using shell history is to use:

^old^new
 
to replace string old by new in last command. The only problem is that it replaces only first appearance. But there is another command replacing all string old by new:

!:gs/old/new/