ULTIMATE stdin, stdout Guide: How To Duplicate stdin, stdout in BASH/Redirect Output to 2 files, Send Output to Multiple Variables and more!

Quick heads up: everything in Linux is a file. You can write, read, delete and wait for a file.

Everything you type in the terminal is symlinked to /dev/pts/1.

stdin goes to /dev/fd/0 and then /dev/pts/1
stdout goes to /dev/fd/1 and then /dev/pts/1
stderr goes to /dev/fd/2 and then /dev/pts/1

Funnily enough, all 3 of them eventually go to /dev/pts/1.

Why? I thought they were different? Not so much.

This is the reason you can see them on terminal!

Here’s the simplest example of duplicating stdout

tee /dev/fd/1 <<< foo
# foo
# foo

Tee automatically created another file descriptor for us.

Now watch this!
This is the same as the above

echo hi | tee /dev/fd/1
# hi
# hi

Instead of sending it back to stdout, send it to stderror!

echo hi | tee /dev/fd/2
# hi
# hi

Now watch when we send to /dev/fd/3 or /dev/fd/0

echo hi | tee /dev/fd/3
# hi

Wtf? Why didn't this print two "hi"s?

Well, because fd/3 doesn't go anywhere useful.

The terminal isn't programmed to read from /dev/fd/3, unless you instruct it.

When you pipe the command to fd 0, the command never ends because it hasn't reached the end of the input.

Watch when you pipe to /dev/0: inputting, and inputting, and inputting!

echo hi | tee /dev/fd/0
# hi
# hi
# hi
# hi
# hi
# hi
# hi
...

The reason these scenarios happen is because that's just the way the terminal is designed.

0 for input
1 for output
2 for error

All 3 of them go to /dev/pts/1 eventually.

If you try piping hi to /dev/fd/2 and then redirecing stderror to /dev/null it won't show you the second command like before.

# normal way to hide error
mount foo bar
# mount point does not exist.
mount foo bar 2>/dev/null

It's just a file bro.

echo hi | tee /dev/fd/2
# hi
# hi
echo hi | tee /dev/fd/2 2>/dev/null
# hi
echo hi | tee /dev/fd/2 2>/dev/null >/dev/null
#


That’s because all 3 of the file descriptors are actually very arbitrary. You can control them like this:

# all the same
echo 1 > file
echo 1 1> file
echo 1 1>&1 file
echo 1 1>&1 1>&1 1>&1 file

You can control them very easily too

1> is the same as >/dev/pts/1

All of these are the same thing

echo hi
# hi
echo hi > /dev/fd/1
# hi
echo hi 1> /dev/fd/1
# hi
echo hi 1> /dev/pts/1
# hi

This command should make sense now:

# echo 1, but send fd/1 as fd/0 for the next command
echo 1 | echo "$( < /dev/fd/0 )" 
# 1

Lets rewrite that

# echo 1, but send the output to fd/0, in a subshell, which reads fd/1 as commands
$(echo 1 >&0)
# 1
$(echo 1)
# 1: command not found
$(echo 1 1>&1)
# 1: command not found
$(echo 1 1>&0)
# 1

Same thing! 1>&0 is really just >/dev/fd/0

$(echo 1 1>&/dev/fd/1)
# 1: command not found
$(echo 1 1>&/dev/fd/0)
# 1
$(echo 1 >&/dev/fd/1)
# 1: command not found
$(echo 1 >&/dev/fd/0)
# 1

Lastly, another way to double the input of two different commands commands

Double stdin to one stdout

printf foo; printf bar;
( printf foo; printf bar; )  >/dev/fd/1
( printf foo; printf bar; )  1>&1
( printf foo; printf bar; )  1>&/dev/fd/1
( printf foo; printf bar; )  1>&/dev/pts/1
( printf foo; printf bar; )  | xargs printf
# foobar
# reverse it: send to subshell, print bar, hear from print foo
( printf foo & printf bar )  | xargs printf
# barfoo

Finally, we approach two commands to one output:

# before
( comm1; comm2; ) 1>&1
# or
( comm1; comm2; ) > file

Send one command to two outputs

You actually require two processes to do this.
/dev/pts/1 cannot say two things at the exact same time, it must be sequential.

Note: $REPLY is the default variable for "read" command if you don't set one.

read <<< 1 
echo $REPLY
1
read < <(echo 1)
echo $REPLY
1
read var1 var2 < <(echo 1 2)
echo $var1
1
echo $var2
2
# consider this
read < <(echo foo)
echo $REPLY > dest1
echo $REPLY > dest2

# same as this
read <<< $(echo foo)
echo $REPLY > dest1
echo $REPLY > dest2

# and this
read <<< foo ; ( echo $REPLY > dest1 ); ( echo $REPLY > dest2 );

# and logical
echo foo | tee >(read; echo "internal ${REPLY}") >(read; echo "internal2 ${REPLY}")

# to multiple files
echo foo | tee >(read; echo $REPLY >test1) >(echo $REPLY >test2)
tee >(read; echo $REPLY >test1) >(echo $REPLY >test2) <<< foo

# the best
tee -a test1 -a test2 <<< foo

Pipe command to two places

echo foo | tee >(read; echo "internal ${REPLY}") >(read; echo "internal2 ${REPLY}")
# foo
# internal foo
# internal2 foo

Pipe to two variables (assign stdout to 2 variables)

# you can't read the subshell variables
echo foo | tee >(read var1) >(read var2)
echo $var1
# 

# you cant read the subshell variables...

# unless... we create more /dev/fd's!
exec 300<>/tmp/300
exec 301<>/tmp/301
echo foo | tee -a /dev/fd/300 -a /dev/fd/301

# Now the message sits in the pipe, waiting to be read.
read < /dev/fd/300 && echo $REPLY
foo
read -u 301 && echo $REPLY
foo

Read stdin and stdout across subshells and terminals

# T1
exec 301<>/tmp/301
while true; do
    read -N 20 < /dev/urandom
    echo ${REPLY//[^[:ascii:]]/} > /dev/fd/301
    sleep 5
done

# T2
tail --follow=descriptor 301
# abc
# def
# etc

Triple bash stdoutput

tee -a /dev/fd/1 -a /dev/fd/1 <<< foo
# foo
# foo
# foo

BONUS! Echo command/stdout into other terminal window! Tell one pts to talk to another pts.

Open two terminals.

ls -lha /dev/pts
[[email protected] ~]$ ls -lha /dev/pts
total 0
drwxr-xr-x  2 root root      0 Jun  1 14:49 .
drwxr-xr-x 22 root root   3.7K Jun  1 14:49 ..
crw--w----  1 user tty  136, 0 Jun  1 22:59 0
crw--w----  1 user tty  136, 1 Jun  1 19:18 1
crw--w----  1 user tty  136, 2 Jun  1 19:22 2
c---------  1 root root   5, 2 Jun  1 14:49 ptmx
echo 1 > /dev/pts/1
echo 2 > /dev/pts/2

And you'll see the command in the other terminal (TTY) where PTS is pseudo terminal.

Puppeteer & NodeJS Timeout Killswitch (SIMPLE!)

The following will always kill a node script after a timeout duration is reached.

There are two ways to kill a nodejs/npm script: inside the node script, or inside the shell.

Consider the following script running direct from the command line:

node << EOF
const puppeteer = require('puppeteer-extra')
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
puppeteer.launch(
    { args: [ '--no-sandbox', '--disable-setuid-sandbox' ], headless: true }
  ).then(async browser => {
  const page = await browser.newPage()
  await page.goto("https://google.com/${username}", {
    waitUntil : ['load', 'domcontentloaded'] 
  });
  let bodyHTML = await page.evaluate(() => document.body.innerHTML);
  console.log(bodyHTML)
  await browser.close();
});
EOF

This simple script returns the HTML of google.com to stdout.

However, if the page fails to load, the script will never timeout and your app or container will hang forever.

We can try the following things:

This will put nodejs into a subshell. It will record the process id of node, sleep 5 and then kill it no matter what.
Method 1: precise PID but no output captured

# use this kill switch if you don't care about the node output
node file.js & { PID=$!; sleep 5; kill $PID 2> /dev/null; }

However, what if we want to use the output of the nodejs file?

Since we cannot read the variables from the subshell, we need to run the PID script first.

Method 2: closes node by name but you can capture the shell variable!

# use this kill switch if you are capturing the nodejs output in a variable
{ sleep 5; pgrep node | xargs kill 2> /dev/null ; } &
OUTPUT_VAR="$(node file.js)"

Method 2.5: Dynamic nodejs script variables using bash and native shell killswitch

# Use this kill switch if you are capturing the nodejs output in a variable
# and use want to use End of Function tags to dynamically change your node script (${username})
{ sleep 5; pgrep node | xargs kill 2> /dev/null ; } &
OUTPUT_VAR="$(node << EOF
    const puppeteer = require('puppeteer-extra')
    const StealthPlugin = require('puppeteer-extra-plugin-stealth');
    puppeteer.use(StealthPlugin());
    puppeteer.launch(
        { args: [ '--no-sandbox', '--disable-setuid-sandbox' ], headless: true }
      ).then(async browser => {
      const page = await browser.newPage()
      await page.goto("https://instagram.com/${username}", {
        waitUntil : ['load', 'domcontentloaded'] 
      });
      let bodyHTML = await page.evaluate(() => document.body.innerHTML);
      console.log(bodyHTML)
      await browser.close();
    });
EOF
)"

In any of the above cases, if your script finishes successfully nothing will happen, unless you launch another node within the race condition window!

Docker: Install NodeJS and Yarn in Ubuntu 18.04

You will need BOTH yarn and nodejs from external resources because the Ubuntu repo versions are very old.

RUN apt install curl gnupg2 -y
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt update -y
RUN apt install nodejs yarn -y

How To Install QEMU on CentOS 8

Easy installation, QEMU is under libvirt.

dnf update -y
dnf install epel-release -y
dnf-config-manager --enable epel-testing --enable PowerTools
dnf install libvirt -y

You can also check if virtualization is available on this host:

[[ $(egrep -c '(svm|vmx)' /proc/cpuinfo) -gt 0 ]] || echo KVM not possible on this host

How To Install Kubernetes on CentOS 8 (Easy Way) + Docker.

This is the simplest way of installing Kubernetes on CentOS 8.
Rancher is an awesome company that makes auto-installers among other software.

They have something called k3s which sets you up, ready for containers.

yum update -y
yum install -y container-selinux selinux-policy-base
rpm -i https://rpm.rancher.io/k3s-selinux-0.1.1-rc1.el7.noarch.rpm
curl -sfL https://get.k3s.io | sh -
# takes maybe 30 seconds
kubectl get node

Here’s your node.

[[email protected] module]# kubectl get node
# NAME                       STATUS   ROLES    AGE     VERSION
# centos-8cpu-32gb-sg-sin1   Ready    master   2m20s   v1.18.2+k3s1

Connect it to your master, or keep it as agent.

sudo k3s server & 
# Kubeconfig is written to /etc/rancher/k3s/k3s.yaml
sudo k3s kubectl get node

# On a different node run the below. NODE_TOKEN comes from /var/lib/rancher/k3s/server/node-token
# on your server
sudo k3s agent --server https://myserver:6443 --token ${NODE_TOKEN}

Install Docker if you want:

dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
DOCKERVERSION=$(dnf list docker-ce --showduplicates | \
sort -r | \
grep "docker-ce.x86_64" -m1 | \
sed -e 's/docker\-ce\.x86\_64//g' -e 's/docker\-ce\-stable//g' | \
tr -d [:space:])
dnf install --nobest docker-ce -y

	

TUN/TAP Deleted/Broken Linux: qemu-system-x86_64: could not open /dev/net/tun: Permission denied

Recently, playing around with my network settings, I encountered a few issues, that I have documented below for you, and how I fixed them.

qemu-system-x86_64: could not open /dev/net/tun: Permission denied

This means that your /dev/net/tun file has the incorrect permissions

The correct permissions are crw-rw-rw-

The following snippet removes the regular file, if any, and creates a Linux special file NAME of the given TYPE.

Note your current permissions and compare them with the following output:

ls -l /dev/net
# crw-rw-rw- 1 root root 10, 200 May 18 23:34 tun

If your /dev/net/tun does not look like that, run the following to recreate that special file:

sudo rm /dev/net/tun
sudo mknod /dev/net/tun c 10 200
sudo chmod 666 /dev/net/tun

# check again now
ls -l /dev/net

chmod: changing permissions of ‘/dev/net/tun’: Operation not permitted

This means you’re most like not super-user (sudo).

However, you should never see this message unless you were trying to run:

sudo chmod 666 /dev/net/tun

Once you’ve correctly re-created /dev/net/tun, you can modprobe it:

sudo modprobe tun

If you received an error here, there are other issues, possibly there is a network profile, service or card that is incompatible with your current kernel.

You should see the following:

grep CONFIG_TUN /usr/src/linux/.config
# CONFIG_TUN=m

This command will verify what’s in your kernel ring buffer:

dmesg | grep TUN
# [   12.3456] tun: Universal TUN/TAP device driver, 1.6

If you’ve passed all of the above steps, then TUN is ready, it’s just needs to be turned on!

sudo modprobe tun

modprobe: FATAL: Module tun not found in directory /lib/modules/…

This error meansy you are missing the tun.ko.xz or tun.ko.gz in your kernel modules. This can be because your current kernel is not compatible with the current version of the TUN/TAP device driver. This would generally only ever occur in Linux distros with Rolling Release models. It should never happen on a stable or long-term distro such as Ubuntu, Debian, CentOS, or RHEL.

If you received that error in a stable distro, then check if the header is there first:

find /lib/modules -iname tun\.*
# /lib/modules/5.6.13-arch1-1/build/include/config/tun.h
# /lib/modules/5.6.13-arch1-1/kernel/drivers/net/tun.ko.xz

If you see only one of the above lines when you run that command, tun was BUILT but not LOADED, try rebooting.

You can try removing any network related kernel packages you’ve installed.

Check for any added kernel modules that didn’t load.

dkms status

for any

Try simply updating or upgrading your system.
If you receive an error when you run sudo modprobe tun:,

modprobe: FATAL: Module tun not found in directory /lib/modules/5.6.11-arch1-1

Always reboot after installing or updating kernel modules, or the Linux kernel itself.

After rebooting (if you failed the above command) you can see if it’s installed now:

find /lib/modules | grep tun\.ko\..z | xargs sudo insmod
# insmod: ERROR: could not insert module /lib/modules/5.6.13-arch1-1/kernel/drivers/net/tun.ko.xz: File exists

That mean’s it’s installed and loaded.

sudo modprobe tun

Leave a comment if you need help, I usually respond within the day 🙂

Linux Lenovo WiFi Card Linux Not Working, rtl8821ce, Slow Boot Process, A Job is running 1 minute 30 seconds.

A start job is running for sys subsystem/net/devices/wlp2s0… 1 minute 30 seconds…

I started seeing these boot errors after updating recently, around 5.6.x Kernel version.

It seems that during update, the DKMS module for my unsupported Lenovo WiFi card (still not in kernel) wasn’t being rebuilt.

If you already have rtl8821ce installed via the Git, then you should try to download the latest version.

Later, I was trying to use /dev/net/tun and was getting additional errors.

Solution: Reinstall rtl8821ce using yay.

Do not attempt this if you do not have Ethernet or mobile hot-spot available  because you will not be able to connect to WiFi during the reinstallation.

$ dkms status
# rtl8821ce, 1.0.5.r65.g55b90f4: added
# rtl8821ce, 1.0.5.r76.g7c4f827: added

Two versions of the rtl8821ce dkms module are in my dkms status, however neither are installed.

# you will lose wifi when you do this, warning.
sudo pacman -R rtl8821ce-dkms-git

# reboot then,
reboot

# ...reinstall
yay rtl8821ce-dkms-git

After I reinstalled rtl8821ce-dkms-git from the AUR, my output was fixed:

$ dkms status
# rtl8821ce, 1.0.5.r65.g55b90f4: added
# rtl8821ce, 1.0.5.r76.g7c4f827: added
# rtl8821ce, 1.0.5.r95.g69765eb, 5.6.13-arch1-1, x86_64: installed

WiFi is back, and the long start errors have gone, thank you Tomas !

Reinstalling rtl8821ce fixed the TUN errors

TUN/TAP Errors Lenovo WiFi

grep CONFIG_TUN /usr/src/linux/.config

You should see the following:

CONFIG_TUN=m

If you do, that means tun is a compiled module. This is normal

dmesg | grep TUN
# [   12.3456] tun: Universal TUN/TAP device driver, 1.6

This means TUN is working fine.

Now, turn it on:

sudo modprobe tun

If your output is FATAL, then you need to reinstall the rtl8821ce WiFi card

modprobe: FATAL: Module tun not found in directory /lib/modules/5.6.11-arch1-1

Turn it on:

sudo insmod /lib/modules/5.6.13-arch1-1/kernel/drivers/net/tun.ko.xz

insmod: ERROR: could not insert module /lib/modules/5.6.13-arch1-1/kernel/drivers/net/tun.ko.xz: Invalid module format

Now, in my case the reason was the following:

I reinstalled rtl8821ce-dkms-git using yay.

After removing this DKMS module, and rebooted, the problem was not fixed.

So I reinstalled the dkms module and now now I have WiFi.

Is TUN fixed too?

sudo modprobe tun

Bingo! TUN back!

Leave a comment if you need help 🙂

How To Install NFS on CentOS 8 & Ubuntu 20.04+: Network Shared Folder Between VPS/Server

NFS allows you to share a directory with another server/client. It’s awesome.

This works great for VPC, or virtually connected servers, like Vultr Private Networking NFS.

In this tutorial we are connecting a CentOS 8 to an Ubuntu 18.04

I will show you how to do it both directions!

Note we are ALSO installing the client and server on BOTH. Kind of like a loop!

/mnt/share is the place we are going to read from
/opt/share is the place we are going share from

Add both server IP’s to the /etc/hosts on both machines. Use the internal IP’s if you are doing Private Networking.

# CentOS/RHEL 8 
yum install epel-release -y
yum install nfs-utils nfs4-acl-tools -y
# Ubuntu
apt-get install nfs-kernel-server nfs-common -y

Then we need to have both of their IP’s on the hosts files.

# on both machines
echo << EOF >> /etc/hosts
11.11.11.11 centos.guy
22.22.22.22 ubuntu.guy
EOF

Now, have to pick one to share and which one to receive.

In this example I am sharing Ubuntu’s disk.
Then at the bottom I have another example of the opposite direction.

Share an Ubuntu 18.04 Drive via NFS

# Exporting server (Ubuntu 18.04)
mkdir /opt/share
chown nobody:nogroup /opt/share
chmod 755 /opt/share
cat << EOF >> /etc/exports
/opt/share           22.22.22.22(rw,sync,no_subtree_check)
EOF

exportfs -r

# open firewall to 11.11.11.11, or the IP that is going to read my drive
ufw allow proto any from 11.11.11.11

CentOS will read it.

Read a CentOS 8 Drive via NFS

# Mounting server (CentOS 8)

mkdir /mnt/share
mount -t nfs 11.11.11.11:/opt/share /mnt/share

Share a CentOS 8 Drive via NFS

Doing the opposite (Share CentOS drive and read on Ubuntu)

# Exporting server (CentOS 8)
mkdir /opt/share
chown nobody:nobody /opt/share
chmod 755 /opt/share
cat << EOF >> /etc/exports
/opt/share           11.11.11.11(rw,sync,no_subtree_check)
EOF

exportfs -r

# open firewall to 22.22.22.22, or the IP that is going to read my drive
firewall-cmd --zone=home --add-rich-rule='rule family="ipv4" source address="22.22.22.22" accept'
firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --permanent --add-service=mountd
firewall-cmd --reload

# start the server
systemctl enable --now nfs-server

Read an Ubuntu Drive via NFS

# Mounting server (Ubuntu 18.04)
mkdir /mnt/share
mount -t nfs 22.22.22.22:/opt/share /mnt/share

The only difference between the two sets of commands above are the following:

On CentOS you must chown the sharing folder as nobody:nobody
On Ubuntu you must chwon the sharing folder as nobody:nogroup

On CentOS you must run systemctl enable –now nfs-server
On Ubuntu you dont need to start the server

On CentOS 8, firewall-cmd is used to allow incoming connections.
On Ubuntu 18.04+, ufw is used to allow incoming connections.

How To UPGRADE GlusterFS (ALL VERSIONS) On Ubuntu (16.04, 18.04, 20.04)

The default glusterfs package in the Ubuntu repo is 3.5.

This is a very old version of GlusterFS.

If you want a newer version you need to do the following:

GLUSTER_VERSON=7

apt update -y
apt-get install software-properties-common
add-apt-repository ppa:gluster/glusterfs-${GLUSTER_VERSON} -y
apt install glusterfs-client glusterfs-common glusterfs-server -y

systemctl enable glusterd
systemctl start glusterd

gluster --version

To see the available versions, run the following:

add-apt-repository ppa:gluster/glusterfs-

How to Install GlusterFS On CentOS 8

On CentOS 8, glusterf, glusterfs-fuse, glusterfs-server all are part of the epel-testing repo.

# centos 8

yum install centos-release-gluster -y
yum install yum-utils -y
yum install epel-release -y
yum-config-manager --enable epel-testing --enable PowerTools

yum install glusterfs-server -y