System calls, shell commands and programs - shell

I'm trying to understand how programs, shell commands and operating
systems work. Please excuse my ignorance since I'm new to this.
When I use a C compiler on the command line, when I type cc
[filename] , I suppose the shell uses a fork() system call to
duplicate its process and then an exec() system call will load the
cc compiler executable into the child process' core image. Then the
child process containing the cc executable will do its thing while
the parent process executing the shell waits, or not. Is it right?
What about shell commands like cp, mv, ls, and others. What are they?
are they executable programs that will also be executed in a new
child process forked by the shell?
What about shell scripts? Suppose I create a simple shell script
like this one (please ignore any errors I dont know how to do this
yet) :
echo "Hello"
date
echo
cc -o test file1.c file2.c file3.c
and then I execute this script using the command line. Will the
command line fork() a new process and exec() this script in the new
process? And then will this new process containing the script fork()
other processes to execute date, cc compiler, etc ??
I hope this doesn't sound too confusing, because I am =/.

Yep! You've got the idea.
When I use a C compiler on the command line, when I type cc [filename] , I suppose the shell uses a fork() system call to duplicate its process and then an exec() system call will load the cc compiler executable into the child process' core image. Then the child process containing the cc executable will do its thing while the parent process executing the shell waits, or not. Is it right?
That's right. The parent process (the shell) calls wait() on the child's PID and waits for it to exit.
What about shell commands like cp, mv, ls, and others. What are they? are they executable programs that will also be executed in a new child process forked by the shell?
Same thing. These are binaries just like a compiler, and the shell does the same thing for them.
Now there are some commands which aren't external binaries known as "built-ins". These are commands that the shell recognizes itself and doesn't need to call an external binary for. Why?
Some have special syntax, like if and while, and so by necessity must be built into the shell.
Some, like cd and read, change the shell process's state, and so must be built-ins. (It's impossible for an external binary to change the shell's current directory since forked processes can only change their own PWD, not their parent's.)
Others, like echo and printf, could be separate binaries, and just happen to be implemented by the shell.
Here's a full list of bash builtins I got from typing help:
job_spec [&] history [-c] [-d offset] [n] or history -anrw [filename] or histor>
(( expression )) if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [>
. filename [arguments] jobs [-lnprs] [jobspec ...] or jobs -x command [args]
: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill>
[ arg... ] let arg [arg ...]
[[ expression ]] local [option] name[=value] ...
alias [-p] [name[=value] ... ] logout [n]
bg [job_spec ...] mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callbac>
bind [-lpvsPVS] [-m keymap] [-f filename] [-q name] [-u name] [-r k> popd [-n] [+N | -N]
break [n] printf [-v var] format [arguments]
builtin [shell-builtin [arg ...]] pushd [-n] [+N | -N | dir]
caller [expr] pwd [-LP]
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars>
cd [-L|[-P [-e]]] [dir] readarray [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callb>
command [-pVv] command [arg ...] readonly [-aAf] [name[=value] ...] or readonly -p
compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W w> return [n]
complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G gl> select NAME [in WORDS ... ;] do COMMANDS; done
compopt [-o|+o option] [-DE] [name ...] set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
continue [n] shift [n]
coproc [NAME] command [redirections] shopt [-pqsu] [-o] [optname ...]
declare [-aAfFgilrtux] [-p] [name[=value] ...] source filename [arguments]
dirs [-clpv] [+N] [-N] suspend [-f]
disown [-h] [-ar] [jobspec ...] test [expr]
echo [-neE] [arg ...] time [-p] pipeline
enable [-a] [-dnps] [-f filename] [name ...] times
eval [arg ...] trap [-lp] [[arg] signal_spec ...]
exec [-cl] [-a name] [command [arguments ...]] [redirection ...] true
exit [n] type [-afptP] name [name ...]
export [-fn] [name[=value] ...] or export -p typeset [-aAfFgilrtux] [-p] name[=value] ...
false ulimit [-SHacdefilmnpqrstuvx] [limit]
fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command] umask [-p] [-S] [mode]
fg [job_spec] unalias [-a] name [name ...]
for NAME [in WORDS ... ] ; do COMMANDS; done unset [-f] [-v] [name ...]
for (( exp1; exp2; exp3 )); do COMMANDS; done until COMMANDS; do COMMANDS; done
function name { COMMANDS ; } or name () { COMMANDS ; } variables - Names and meanings of some shell variables
getopts optstring name [arg] wait [id]
hash [-lr] [-p pathname] [-dt] [name ...] while COMMANDS; do COMMANDS; done
help [-dms] [pattern ...] { COMMANDS ; }
Aside from built-ins there are also functions and aliases. These are ways of defining new functionality without having to create separate scripts/binaries.
uppercase() {
tr '[:lower:]' '[:upper:]' <<< "$*"
}
alias ls='ls --color=auto -F'
Functions and aliases are usually for convenience or to add supplementary functionality.
What about shell scripts? ... Will the command line fork() a new process and exec() this script in the new process? And then will this new process containing the script fork() other processes to execute date, cc compiler, etc ??
Yes, exactly right. When a shell script is run the parent shell forks a child process and the script runs there. The commands in the script and therefore forked off of this child process; they are grandchildren of the original shell.

When you execute a shellscript, it does fork off and create a new shell, interpreting each command through a separate fork/exec mechanism. However, there are some shell builtins, for example, echo may be built into some shells even when it is available as an executable in /usr/bin. cp and mv are indeed executables that are executed through fork/exec mechanism. One thing you may have missed is that the executables need to be in a directory contained in your PATH variable. Try renaming a hello world code executable in your current directory as ls and specify your current directory (.) as the first one in your path. You can also find out about the executables using type and which commands.

Related

"read: -a: unknown option" when running script with ksh

Hello i was able to run one of the script from putty terminal without any issue as follows
sh file_validation_generic.sh 1 /test/data/infa_shared/dev/scripts
but when i try to execute the script from a tool it is giving below error
sh -c "ksh /test/data/infa_shared/dev/scripts/file_validation_generic.sh 1 /test/data/infa_shared/dev/scripts"
error :-
/test/data/infa_shared/dev/scripts/file_validation_generic.sh[121]: read: -a: unknown option
Usage: read [-ACprsSv] [-d delim] [-u fd] [-t timeout] [-n count] [-N count]
[var?prompt] [var ...]
this error occurs in a while loop during the read to an array
IFS="~"
read -a star <<< "$line"
col_pos=${star[1]}
col_patt=${star[6]}

Bash - Is there a way to replace a bash command with parameters every time is typed with another parameter?

I am working on a HPC running slurm and CentOS. My workflow software (Nextflow v19.10.0) needs to execute this command
squeue --noheader -o %i %t -t all -u username
However, I have an error raises the following error
squeue: error: Unrecognized option: %
Usage: squeue [-A account] [--clusters names] [-i seconds] [--job jobid] [-n name] [-o format] [-p partitions] [--qos qos] [--reservation reservation] [--sort fields] [--start] [--step step_id] [-t states] [-u user_name] [--usage] [-L licenses] [-w nodes] [--federation] [--local] [--sibling] [-ahjlrsv]
Is there a way to wrap the above command in my .bashrc file, so when ever Nextflow runs the command it would automatically turned into this, which I have tested to work on my cluster?
squeue --noheader -o "%i %t" -t all -u username
Thanks so much for your help!!!
If Nextflow is running bash (the shell you tagged this question for), not /bin/sh (which is more common, as it's what the system() library call in many languages invokes), you can do this in any enclosing shell:
# override *any* call to squeue with a very specific command that's known to work
squeue() {
printf 'Ignoring old squeue arguments: ' >&2
printf '%q ' "$#" >&2
printf '\n' >&2
command squeue --noheader -o '%i %t' -t all -u username
}
export -f squeue
However, that probably won't work: It's likely that Nextflow is actually using sh instead, so instead of using an exported function, you'll want to create a directory with a squeue executable script in it that then invokes the real squeue command. Thus:
#!/bin/bash
printf 'Ignoring old squeue arguments: ' >&2
printf '%q ' "$#" >&2
printf '\n' >&2
# FIXME: replace /usr/bin/squeue with the actual location of the real command
exec /usr/bin/squeue --noheader -o '%i %t' -t all -u username

Using pkill, how can I find out how many processes matched

We may not know how many processes actually died as a result of a pkill command, but is there a way to echo how many processes matched?
pkill -f "bin/ql_node_server"
is there a switch to pkill that will echo how many processes matched?
on MacOS, if I use the -c option with pkill, I get:
pkill: illegal option -- c
usage: pkill [-signal] [-ILfilnovx] [-F pidfile] [-G gid]
[-P ppid] [-U uid] [-g pgrp]
[-t tty] [-u euid] pattern ...
You can use -c flag.
From man pkill:
-c, --count
Suppress normal output;
instead print a count of matching processes.
When count does not match anything, e.g. returns zero, the command will return non-zero value.

Cant run bash script example from "The linux command line, a complete introduction, 2nd"

Here is an example of my zsh script, that script was modified from original one:
#!/bin/sh
set -e
# read-default.sh: supply a default value if user preses Enter key.
read -e -p "What is your user name? " -i $USER
echo "You answered: '$REPLY'"
Here is an original script from the book:
#!/bin/bash
# read-default: supply a default value if user presses Enter key.
read -e -p "What is your user name? " -i $USER
echo "You answered: '$REPLY'"
Here is a mistake what I've got after running zsh script:
read-default.sh: line 7: read: -i: invalid option
read: usage: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array]
[-n nchars] [-d delim] [name ...]
I will be appreciate for any help! :)
What language is your script written in?
Shell scripts written in bash, zsh, and sh differ slightly.
It seems your are using bash features, but run the code with sh.
The read command is a shell builtin. Have a look at the corresponding man pages to find out what kind of options are allowed for your shell.
For your example it should be enough to change the shebang (first line) back to #!/bin/bash or run the script explicitly with bash read-default.sh.
As you see in your shell's error message, you cannot use the -i flag when calling read. To solve your problem you need to check if $REPLY is empty and supply a default value if it is.

bash script/pgrep not working as expected

I have a bash script that tries to call pgrep with arguments (Over simplified):
PATTERN="'/opt/apps/bin/lighttpd.bin -f /opt/apps/etc/lighttpd/lighttpd.conf\$'"
pgrep -f $PATTERN
echo pgrep -f $PATTERN
Gives the following output:
Usage: pgrep [-cflvx] [-d DELIM] [-n|-o] [-P PPIDLIST] [-g PGRPLIST] [-s SIDLIST]
[-u EUIDLIST] [-U UIDLIST] [-G GIDLIST] [-t TERMLIST] [PATTERN]
pgrep -f '/opt/apps/bin/lighttpd.bin -f /opt/apps/etc/lighttpd/lighttpd.conf$'
I suppose it means the argument is not passed to pgrep but is passed to echo for some reason.
What I'm expecting:
7632
pgrep -f '/opt/apps/bin/lighttpd.bin -f /opt/apps/etc/lighttpd/lighttpd.conf$'
When I run the preg line by itself, it outputs 7632 as expected.
Am I doing something wrong here? I've tried with sh, dash and bash. Same outcomes, I really don't see the problem.
You need to surround PATTERN in double quotes:
PATTERN="/opt/apps/bin/lighttpd.bin -f /opt/apps/etc/lighttpd/lighttpd.conf\$"
pgrep -f "$PATTERN"
See: quoting variables
Edit: and for echoing i would just do:
echo pgrep -f \'$PATTERN\'
As I don't have lighttpd.bin available to test with, I am submitting an untested option, mostly agreeing with #barti_ddu, but with a slightly different twist
PATTERN='/opt/apps/bin/lighttpd.bin -f /opt/apps/etc/lighttpd/lighttpd.conf\$'
pgrep -f "$PATTERN"
echo pgrep -f "$PATTERN"
I would keep the single quotes on the assingment to PATTERN, but totally agree you need the dbl-quoting when using with pgrep or echo.
I hope this helps.

Resources