Bash capture a keystroke from another terminal - bash

Using bash 4.3 and root powers, I want to be able to detect one keystroke (any key) from another terminal.
In terminal 1, a background process that writes to a named pipe once one character was read from another tty, say pts/18
read -rsn1 < /dev/pts/18 && echo > ./myfifo &
In terminal 2, an attempt to read a character from the same fifo
read -rsn1 < ./myfifo
It works moderately well, but only after several (3 or 4) keystrokes in pts/18, not the first one.
The mechanism appears to be redundant but it allows to start several background processes with different ttys and redirect to the same named pipe.
Hope you can help me.

One day I've tried to do that.
Wrote script who getting that you want with strace:
#!/bin/bash
# Couple of special symbols
BACKSPACE='\10\33[K'
BACKSPACE_EMPTY='\7'
BACKSLASH='\\'
QUOTE="\'"
DOUBLE_QUOUTE='\"'
LARROW='\10'
RARROW='\33[C'
#APPOSTRO="'\`'"
BACKSPACE_='\\33\[1P'
#LOGDATE=`date '+%Y/%m/%d %H:%M:%S'`
BADBIN='\\33\]'
while read -r line
do
# Avoiding binary symbols
NOBINline=$(echo "${line}" | strings)
# Current `pwd` value for session
CURRENT_PWD_OF_PID=$(readlink -f /proc/${1}/cwd)
# Getting username
USER_OF_PID=$(cat /proc/${1}/environ | strings | tr '.' ' ' | grep USER | awk -F= '{print $2}')
# Not the best but working way to create prompt line
HOSTNAME_OF_PID=`hostname -a`
STR_TO_REMOVE=$(printf "${USER_OF_PID}""#""${HOSTNAME_OF_PID}"":""${CURRENT_PWD_OF_PID}")
# Cut useless symbols from strace output.
parsed_line=$(echo "${NOBINline}" | perl -nale 'print $1 if ~/\"(.*)\"/gi')
if [ "${parsed_line}" == "\n" ]
then
parsed_line="{{ENTER}}"
fi
output_line=''
inchar_line=''
postinchar_line=''
inchar_line=$(printf "${parsed_line}")
if [ "${inchar_line}" == "{{ENTER}}" ]
then
echo ""
else
output_line=$(printf "${output_line}""${inchar_line}")
fi
if [ "${output_line}" != "${CURRENT_PWD_OF_PID}" -a "${output_line}" != "${STR_TO_REMOVE}" -a `echo "${NOBINline}" | grep -c "${BADBIN}"` -eq 0 ]
then
printf "${output_line}"
fi
done < <(sudo strace -e trace=write -s1000 -p $1 2>/dev/stdout)
How to use:
./script.sh <PID_of_mointored_console>
Also I've written the second script (much more ugly than first):
#!/bin/bash
while [ 1 ]
do
# lets and log_start -- names of two my scripts.
for PIDs in `ps awwux | grep [p]ts | grep bash | grep -v lets | grep -v log_start | grep -v grep | awk '{print $2}'`
do
PTS=$(ps awwux | grep "${PIDs}" | grep -v grep | awk '{print $7}')
if [ -f /tmp/bash_log.${PIDs} ]
then
sleep 1
else
touch /tmp/bash_log.${PIDs}
# lets.sh -- first script.
/bin/bash /root/lets.sh $PIDs >> /tmp/bash_log.${PIDs} &
fi
# tr -cd '\11\12\15\40-\176' < /tmp/x_bash_log.${PIDs} | sed 's/\]0;//g' | sed 's/\[C//g' > /tmp/bash_log.${PIDs}
done
for IFEMPTY in `find /tmp/ -type f -name 'bash_log*' -mmin +600`
do
if [ `cat "${IFEMPTY}" | grep -v "\'" | wc -c` -lt 2 ]
then
rm -rf "${IFEMPTY}"
else
sleep 1
fi
done
done
Start it in the background, place near first script.
It will catching all new terminal sessions, and start firstscript.sh <PID> for each, and creating /tmp/bash_log.<PID> file.
How to use:
./monitor.sh &
# Start new terminal
[sahaquiel#sahaquiel-PC ~]$ echo $$
916
[sahaquiel#sahaquiel-PC ~]$ HELO, IM A NEW STRING
bash: HELO,: command not found
[sahaquiel#sahaquiel-PC ~]$ tail -n3 /tmp/bash_log.916
916
''[sahaquiel#sahaquiel-PC ~]$ HELO, IM A NEW STRING
'''[sahaquiel#sahaquiel-PC ~]$ tail -n3 /tmp/bash_log.916
NB. I'm still working with it, created just for fun.
/tmp/bash_log files have awful formatting. I've also tried to log in the file backslashes, Ctrl+R, Ctrl+C and the same, but at now it's looks like not readable enough:
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 123[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 12[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 1[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING
'''[sahaquiel#sahaquiel-PC ~]$ NOW I I[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIMES[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIME[LARROW]S[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIM[LARROW]ES[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TI[LARROW]MES
You can change it somehow you want.
There are a lot of variables, I wanted to use them later for extended strings parse way, but it doesn't work now correctly.
P.S. If someone really interested in that, can check this project on my Github (check profile for link)

Related

Print program path and its symlink using which

Whenever I use which I do this: $ which -a npm
Which results in: /usr/local/bin/npm
Then to find the real path, I run:
ls -l /usr/local/bin/npm
I would like a fast way of doing this. The best I have come up with is defining a function:
which(){
/usr/bin/which -a "$#" | xargs ls -l | tr -s ' ' | cut -d ' ' -f 9-
}
Now it has a nice output of: /usr/local/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js
Is there a better way to do this? I don't like using cut like this.
This won't print the -> output ls -l does, but it will resolve symlinks:
which() {
command which -a "$#" | xargs -d '\n' readlink -m
}
If you want the -> output but want to do it more robustly, you could mimic ls -l with:
which() {
command which -a "$#" | while IFS= read -r file; do
if [[ -L $file ]]; then
echo "$file -> $(readlink -m "$file")"
else
echo "$file"
fi
done
}
What does command do?
command suppresses function lookup and runs the built-in which command as if which() weren't defined. This way you don't have to hardcode /usr/bin/which.
awk: if first character is "l" (link), print fields 9,10,11; else only 9.
[ranga#garuda ~]$ ls -l $(which -a firefox)|awk '{print $1 ~ /^l/ ? $9 $10 $11 : $9 }'
/usr/bin/firefox->/usr/lib64/firefox/firefox
$1 is the first field
$1 ~ /^l/ ? tests whether the first field matches the pattern ^l (first character is "l")
if the test passes, print receives $9 $10 $11; else, only $9.
sed : remove first 8 non-space and space character bunches.
[ranga#garuda ~]$ ls -l $(which firefox) | sed 's/^\([^ ]*[ ]*\)\{8\}//'
/usr/bin/firefox -> /usr/lib/firefox/firefox
[ ]* matches a bunch of contiguous spaces
[^ ]* matches a contiguous bunch of non-space characters
the grouping \([^ ]*[ ]*\) matches a text with one non-space bunch and one space bunch (in that order).
\{8\} matches 8 contiguous instances of this combination. ^ at the beginning pins the match to the beginning of the line.
's/^\([^ ]*[ ]*\)\{8\}//' replaces a match with empty text - effectively removing it.
seems to work so long as you aren't running "which" on an alias.
these commands are not presented inside a function but can be used in one (which you already know how to).

Parse file to .aliasrc

I want to transform a string given in this form:
xyx some commands
into this form:
alias xyz="some commands"
I tried different combinations in the terminal. It seems (i'm not sure) that it worked once, but never when i run this from the script. I've read somewhere that this is a variable problem.
Alias for readability:
alias first="sed 's/\s.*//'"
alias rest="sed 's/\S*\s*//'"
cat f_in | tee -a >(one=$(first)) >(two=$(rest)) | tee >(awk '{print "alias "$1"=\""$2"\""}' > f_out )
I used awk in this way to parse "cat f_in" into "print". It doesn't work. Then, i used "awk -v" but it still doesn't work too. How to redirect variable $one and $two into awk:
{one=$(first) === first | read -r one }?
Is this what you're trying to do:
$ echo 'xyx some commands' |
awk '{var=$1; sub(/^[^[:space:]]+[[:space:]]+/,""); printf "alias %s=\"%s\"\n", var, $0}'
alias xyx="some commands"
$ echo 'xyx some commands' |
sed 's/\([^[:space:]]*\)[[:space:]]*\(.*\)/alias \1="\2"/'
alias xyx="some commands"

Basic bash script issue

Here is my first post on the forum, let me know if I may be more descriptif.
Yesterday, I script a lil' script to start, restart and stop my server in one, so I test 3 times the $1 argument to know if it's start, restart, stop string.
I take all the improvments if I can do it in another way :)
Here is my code :
#!/bin/bash
STA="start"
RES="restart"
STO="stop"
SERVERNAME="server_live"
if [ $1 -ge 1 ]
then
echo "Entre un argument : start, stop, restart"
elif [ $1 = $STA ]
then
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
elif [ $1 = $RES ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
elif [ $1 = $STO ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
fi
I got the following error :
My code here
It means : syntaxe error near inexpected symbol elif line 10
Thanks in advance.. I wanna add that
#!/bin/bash
echo "read smthg"
read name
put an error too (on read) , how might I know if I got a version issue or something like that ?
you should use case instead of if ... elif, and (almost) always quote your variables between " " :
Please try this version (close to yours, but using case...esac and with some quotes added):
#!/bin/bash
SERVERNAME="server_live"
case "$1" in
start)
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
;;
restart)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
;;
stop)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
;;
*)
echo "argument: '$1' non reconnu..."
exit 1
;;
esac
If you encounter an error, your script may contain things before that part that interfere? Please first paste your script in : www.shellcheck.net and see what it tells you (it will parse it and show a lot of common error, such as unclosed quotes, etc).
EDIT
The problem came from the fact that person was editing under windows (which uses 'CR-LF' line endings) and using it in linux/unix (which expected just LF, and thus took the CR as an additionnal character in the lines containing them).
To get rid of "everything non-printable" that isn't used for scripting in your script:
LC_ALL="C" tr -cd "[$(printf '\t')\$(printf '\n') -~]" <script.bash >script_without_nonprintables.bash
# LC_ALL="C" just before tr makes tr use that environment, which gives "ascii" instead of whatever locale you use. This helps ensure the ranges given are the ascii ones, and not something else.
# -c = complement, ie "whatever is not..." -d="delete", so -cd= "delete whatever is not specified"
# [a-d] = from a to d in the current locale (ascii, here, thanks to LC_ALL="C", so it will be : a, b, c or d
# in ascii, SPACE to ~ covers all the character you need to write scripts, except for TAB (\t) and Newline (\n), so I added those as well.
# for newline, I preceded it with an "\" to have it taken literally
chmod +x script_without_nonprintables.bash

Efficiently find PIDs of many processes started by services

I have a file with many service names, some of them are running, some of them aren't.
foo.service
bar.service
baz.service
I would like to find an efficient way to get the PIDs of the running processes started by the services (for the not running ones a 0, -1 or empty results are valid).
Desired output example:
foo.service:8484
bar.server:
baz.service:9447
(bar.service isn't running).
So far I've managed to do the following: (1)
cat t.txt | xargs -I {} systemctl status {} | grep 'Main PID' \
| awk '{print $3}'
With the following output:
8484
9447
But I can't tell which service every PID belongs to.
(I'm not bound to use xargs, grep or awk.. just looking for the most efficient way).
So far I've managed to do the following: (2)
for f in `cat t.txt`; do
v=`systemctl status $f | grep 'Main PID:'`;
echo "$f:`echo $v | awk '{print \$3}'`";
done;
-- this gives me my desired result. Is it efficient enough?
I ran into similar problem and fount leaner solution:
systemctl show --property MainPID --value $SERVICE
returns just the PID of the service, so your example can be simplified down to
for f in `cat t.txt`; do
echo "$f:`systemctl show --property MainPID --value $f`";
done
You could also do:
while read -r line; do
statuspid="$(sudo service $line status | grep -oP '(?<=(process|pid)\s)[0-9]+')"
appendline=""
[[ -z $statuspid ]] && appendline="${line}:${statuspid}" || appendline="$line"
"$appendline" >> services-pids.txt
done < services.txt
To use within a variable, you could also have an associative array:
declare -A servicearray=()
while read -r line; do
statuspid="$(sudo service $line status | grep -oP '(?<=(process|pid)\s)[0-9]+')"
[[ -z $statuspid ]] && servicearray[$line]="statuspid"
done < services.txt
# Echo output of array to command line
for i in "${!servicearray[#]}"; do # Iterating over the keys of the associative array
# Note key-value syntax
echo "service: $i | pid: ${servicearray[$i]}"
done
Making it more efficient:
To list all processes with their execution commands and PIDs. This may give us more than one PID per command, which might be useful:
ps -eo pid,comm
So:
psidcommand=$(ps -eo pid,comm)
while read -r line; do
# Get all PIDs with the $line prefixed
statuspids=$(echo $psidcommand | grep -oP '[0-9]+(?=\s$line)')
# Note that ${statuspids// /,} replaces space with commas
[[ -z $statuspids ]] && appendline="${line}:${statuspids// /,}" || appendline="$line"
"$appendline" >> services-pids.txt
done < services.txt
OUTPUT:
kworker:5,23,28,33,198,405,513,1247,21171,22004,23749,24055
If you're confident your file has the full name of the process, you can replace the:
grep -oP '[0-9]+(?=\s$line)'
with
grep -oP '[0-9]+(?=\s$line)$' # Note the extra "$" at the end of the regex
to make sure it's an exact match (in the grep without trailing $, line "mys" would match with "mysql"; in the grep with trailing $, it would not, and would only match "mysql").
Building up on Yorik.sar's answer, you first want to get the MainPID of a server like so:
for SERVICE in ...<service names>...
do
MAIN_PID=`systemctl show --property MainPID --value $SERVICE`
if test ${MAIN_PID} != 0
than
ALL_PIDS=`pgrep -g $MAIN_PID`
...
fi
done
So using systemctl gives you the PID of the main process controlled by your daemon. Then the pgrep gives you the daemon and a list of all the PIDs of the processes that daemon started.
Note: if the processes are user processes, you have to use the --user on the systemctl command line for things to work:
MAIN_PID=`systemctl --user show --property MainPID --value $SERVICE`
Now you have the data you are interested in the MAIN_PID and ALL_PIDS variables, so you can print the results like so:
if test -n "${ALL_PID}"
then
echo "${SERVICE}: ${ALL_PIDS}"
fi

bash: process substitution, paste and echo

I'm trying out process substitution and this is just a fun exercise.
I want to append the string "XXX" to all the values of 'ls':
paste -d ' ' <(ls -1) <(echo "XXX")
How come this does not work? XXX is not appended. However if I want to append the file name to itself such as
paste -d ' ' <(ls -1) <(ls -1)
it works.
I do not understand the behavior. Both echo and ls -1 write to stdout but echo's output isn't read by paste.
Try doing this, using a printf hack to display the file with zero length output and XXX appended.
paste -d ' ' <(ls -1) <(printf "%.0sXXX\n" * )
Demo :
$ ls -1
filename1
filename10
filename2
filename3
filename4
filename5
filename6
filename7
filename8
filename9
Output :
filename1 XXX
filename10 XXX
filename2 XXX
filename3 XXX
filename4 XXX
filename5 XXX
filename6 XXX
filename7 XXX
filename8 XXX
filename9 XXX
If you just want to append XXX, this one will be simpler :
printf "%sXXX\n"
If you want the XXX after every line of ls -l output, you need a second command that output x times the string. You are echoing it just once and therefore it will get appended to the first line of ls output only.
If you are searching for a tiny command line to achieve the task you may use sed:
ls -l | sed -n 's/\(^.*\)$/\1 XXX/p'
And here's a funny one, not using any external command except the legendary yes command!
while read -u 4 head && read -u 5 tail ; do echo "$head $tail"; done 4< <(ls -1) 5< <(yes XXX)
(I'm only posting this because it's funny and it's actually not 100% off topic since it uses file descriptors and process substitutions)
... you have to:
for i in $( ls -1 ); do echo "$i XXXX"; done
Never use for i in $(command). See this answer for more details.
So, to answer of this original question, you could simply use something like this :
for file in *; do echo "$file XXXX"; done
Another solution with awk :
ls -1|awk '{print $0" XXXX"}'
awk '{print $0" XXXX"}' <(ls -1) # with process substitution
Another solution with sed :
ls -1|sed "s/\(.*\)/\1 XXXX/g"
sed "s/\(.*\)/\1 XXXX/g" <(ls -1) # with process substitution
And useless solutions, just for fun :
while read; do echo "$REPLY XXXX"; done <<< "$(ls -1)"
ls -1|while read; do echo "$REPLY XXXX"; done
It does it only for the first line, since it groups the first line from parameter 1 with the first line from parameter 2:
paste -d ' ' <(ls -1) <(echo "XXX")
... outputs:
/dir/file-a XXXX
/dir/file-b
/dir/file-c
... you have to:
for i in $( ls -1 ); do echo "$i XXXX"; done
You can use xargs for the same effect:
ls -1 | xargs -I{} echo {} XXX

Resources