how can i source last N lines of a shell script - unix

for example
a=1;b=2;c=3;d=4;e=5
the file is 1.sh
cat 1.sh
echo $a
echo $b
echo $c
echo $d
echo $e
i use the command
source 1.sh
i get
1
2
3
4
5
how can i source the last 3 line of 1.sh?
tail -n 3 1.sh|xargs -0 sh -c
can not work because the variable can not pass to sh

tail -n 3 1.sh > tmp.sh ; source ./tmp.sh ; rm tmp.sh
If necessary, choose or generate a name that's unlikely to collide with anything else, like /tmp/tmp$$.sh.

Related

Bash delete all folders except one that might be nested

I am trying to work out a way to delete all folders but keep once, even if it is nested.
./release/test-folder
./release/test-folder2
./release/feature/custom-header
./release/feature/footer
If I run something like:
shopt -s extglob
rm -rf release/!(test-folder2)/
or
find ./release -type d -not -regex ".*test-folder2.*" -delete
get it OK, but in the cases when path is nested like feature/footer
Both command lines matches release/feature and it gets deleted.
Can you suggest any other option that would keep the folder, no matter how nested it is?
This is not best solution but it works.
# $1 = root path
# $2 = pattern
findex(){
# create temp dir
T=$(mktemp -d)
# find all dirs inside "$1"
# and save it in file "a"
find "$1" -type d >$T/a
# filtering file A by pattern "$2"
# and save it in file "b"
cat $T/a | grep "$2" >$T/b
# For each path in the file b
# add paths of the parent directories
# and save it in file "c"
cat $T/b | while read P; do
echo $P
while [[ ${#1} -lt ${#P} ]]; do
P=$(dirname "$P")
echo $P
done
done >$T/c
# make list in file "c" unique
# and save it in file "d"
cat $T/c | sort -u >$T/d;
# find from list "a" all the paths
# that are missing in the list "d"
awk 'NR==FNR{a[$0];next} !($0 in a)' $T/d $T/a
# remove temporary directory
rm -rf $T
}
# find all dirs inside ./path except matching "pattern"
# and remove it
findex ./path "pattern" | xargs -L1 rm
Test it
findex(){
T=$(mktemp -d)
find "$1" -type d >$T/a
cat $T/a | grep "$2" >$T/b
cat $T/b | while read P; do
echo $P
while [[ ${#1} -lt ${#P} ]]; do
P=$(dirname "$P")
echo $P
done
done >$T/c
cat $T/c | sort -u >$T/d;
# save result in file "e"
awk 'NR==FNR{a[$0];next} !($0 in a)' $T/d $T/a >$T/e
# output path of temporary directory
echo $T
}
cd $TMPDIR
for I in {000..999}; do
mkdir -p "./test/${I:0:1}/${I:1:1}/${I:2:1}";
done
T=$(findex ./test "5")
cat $T/a | wc -l # => 1111 dirs total
cat $T/d | wc -l # => 382 dirs matched
cat $T/e | wc -l # => 729 dirs to delete
rm -rf $T ./test

Using "declare" in Zsh

I am following this thread: https://stackoverflow.com/a/19742842/5057251
for typeset (or declare) in ZSH, not BASH.
#Declare (or typeset) an array of integers
#declare -ai int_array
typeset -ai int_array
int_array=(1 2 3)
echo "${int_array[#]}"
Then
# Attempt to change 1st element to string. (expect to fail)
int_array[1]="Should fail" || echo "error: ${LINENO}"
echo "${int_array[#]}"
Bash finds the error, gracefully reports error and lineno, prints:
1 2 3
But Zsh accepts, prints:
Should fail 2 3
Not sure why different.
There are two problems here:
In bash, and zsh, assigning a string to an integer variable causes that string to be evaluated as an arithmetic expression. Thus, this is not an error:
$ typeset -i foo
$ foo="bar"
If bar was a variable previously set to an arithmetic expression, then bar's expansion would be evaluated as such:
$ bar=10+2
$ typeset -i foo
$ foo="bar"
$ echo "$foo"
12
The error in your assignment, of course, is that there's no way to expand Should fail like that. If it were, say, Should - fail (an arithmetic expression subtracting the value of the two variables Should and fail, for example, it would still work:
$ foo="Should - fail"
$ echo "$foo"
0
The second problem is that nothing in the zsh docs indicate that -i may be set for an entire array, and so the -a in -ai is ignored:
bash-5.0$ typeset -ai foo
bash-5.0$ declare -p foo
declare -ai foo=([0]="0") # the previous value was retained in the array
vs zsh:
% typeset -ai foo
% foo[1]=10
% foo[2]=20
% declare -p foo
typeset -i foo=20 # treated as a normal variable, not array
What you're seeing is essentially int_array being redeclared as an array (without any qualifiers) when you do int_array=(1 2 3):
% foo=(1 2 3)
% declare -p foo
typeset -a foo=( 1 2 3 )
Using zsh typeset can produce a few possible outcomes:
- no errors, works (yeah!).
- errors, script fails (fix!).
- no errors, but unexpected behavior. (scratch head)
As an example of last category, this produces no errors, but the typeset -p reveals -i is ignored.
{
unset int_array
typeset -ia int_array
int_array=(1 2 3)
echo $? "-Point A"
typeset -p int_array
} always {
echo $? "-Point B"
typeset -p int_array
(( TRY_BLOCK_ERROR=0 ))
}
echo $? "-Point C"
echo "survived"
produces
0 -Point A
typeset -a int_array=( 1 2 3 )
0 -Point B
typeset -a int_array=( 1 2 3 )
0 -Point C
survived
The first line unsets int_array. The typeset command declares
int_array to be both an array and int, which is not what zsh allows. The next
line assigns int_array to a value. There is no error as the $? tells us,
but close examination of final typeset -p int_array reveals what actually
happened.
With a small change, we can produce errors and use the always block and
typeset -p to find more details.
{
unset int_array
typeset -ia int_array=(1 2 3) # error
echo $? "-Point A"
typeset -p int_array
} always {
echo $? "-Point B"
typeset -p int_array
(( TRY_BLOCK_ERROR=0 ))
}
echo $? "-Point C"
echo "survived"
040_declare_version2.sh:typeset:135: int_array: inconsistent type for assignment
1 -Point B
040_declare_version2.sh:typeset:140: no such variable: int_array
1 -Point C
survived
The only difference is int_array was given a value in the faulty typeset -ia statement.
This produces errors, and the script jumps to the always block.
The (( TRY_BLOCK_ERROR=0)) allows the script to continue
and not terminate, but the error is still reported at "Point C".
To check shell version:
$SHELL --version
zsh 5.4.2 (x86_64-ubuntu-linux-gnu)

Format output of concatenating 2 variables in unix

I am coding a simple shell script that checks the space of the target path and the space utilization per directory on that target path (example, I am checking space of /path1/home, and also checks how all the folders on /path1/home is consuming the total space.) My question is regarding the output it produces, it is not that pleasing to the eye (uneven spacing). See sample output lines below.
SIZE USER_FOLDER DATE_LAST_MODIFIED
83G FOLDER 1 Apr 15 03:45
34G FOLDER 10 Mar 9 05:02
26G FOLDER 11 Mar 29 13:01
8.2G FOLDER 100 Apr 1 09:42
1.8G FOLDER 101 Apr 11 13:50
1.3G FOLDER 110 Feb 16 09:30
I just want the output format to be in line with the header so it will look neat because I will use it as a report. Here is the code I am using for this part.
ls -1 | grep -v "lost+found" |grep -v "email_body.tmp" > $v_path/Users.tmp
for user in `cat $v_path/Users.tmp | grep -v "Users.tmp"`
do
folder_size=`du -sh $user 2>/dev/null` # should be run using a more privileged user so that other folders can be read (2>/dev/null was used to discard error messages i.e. "du: cannot read directory `./marcnad/.gnupg': Permission denied")
folder_date=`ls -ltr | tr -s " " | cut -f6,7,8,9, -d" " | grep -w $user | cut -f1,2,3, -d" "`
folder_size="$folder_size $folder_date"
echo $folder_size >> $v_path/Users_Usage.tmp
done
echo "Summary of $v_path Disk Space Utilization per folder." >> email_body.tmp
echo "" >> email_body.tmp
echo "SIZE USER_FOLDER DATE_LAST_MODIFIED" >> email_body.tmp
for i in T G M K
do
cat $v_path/Users_Usage.tmp | grep [0-9]$i | sort -nr -k 1 >> $v_path/email_body.tmp
done
Thanks!
EDIT: Formatting
When you print the data use printf instead of echo
cat $v_path/Users_Usage.tmp | while read a b c d e f
do
printf '%-5s%-7%s%-4s%-4s%-3s-6s' $a $b $c $d $e $f
done
See here

How to turn strings on the command line into individual positional parameters

My main question is how to split strings on the command line into parameters using a terminal command in Linux?
For example
on the command line:
./my program hello world "10 20 30"
The parameters are set as:
$1 = hello
$2 = world
$3 = 10 20 30
But I want:
$1 = hello
$2 = world
$3 = 10
$4 = 20
$5 = 30
How can I do it correctly?
You can reset the positional parameters $# by using the set builtin. If you do not double-quote $#, the shell will word-split it producing the behavior you desire:
$ cat my_program.sh
#! /bin/sh
i=1
for PARAM; do
echo "$i = $PARAM";
i=$(( $i + 1 ));
done
set -- $#
echo "Reset \$# with word-split params"
i=1
for PARAM; do
echo "$i = $PARAM";
i=$(( $i + 1 ));
done
$ sh ./my_program.sh foo bar "baz buz"
1 = foo
2 = bar
3 = baz buz
Reset $# with word-split params
1 = foo
2 = bar
3 = baz
4 = buz
As an aside, I find it mildly surprising that you want to do this. Many shell programmers are frustrated by the shell's easy, accidental word-splitting — they get "John", "Smith" when they wanted to preserve "John Smith" — but it seems to be your requirement here.
Use xargs:
echo "10 20 30" | xargs ./my_program hello world
xargs is a command on Unix and most Unix-like operating systems used
to build and execute command lines from standard input. Commands such as
grep and awk can accept the standard input as a parameter, or argument
by using a pipe. However, others such as cp and echo disregard the
standard input stream and rely solely on the arguments found after the
command. Additionally, under the Linux kernel before version 2.6.23,
and under many other Unix-like systems, arbitrarily long lists of
parameters cannot be passed to a command,[1] so xargs breaks the list
of arguments into sublists small enough to be acceptable.
(source)

echo value inside a variable?

x=102 y=x
means when i echo $y it gives x
echo $y
x --and not 102
and when i echo $x it give 102
lets say I dnt know what is inside y
and i want the value of x to be echoed with using y someting like this
a=`echo $(echo $y)`
echo $a
Ans 102
You need to tell the shell to evaluate your command twice -- once to turn $y into x, and again to get the value of $x. The most portable way I know to do this is with eval:
$ /bin/sh
$ x=100
$ y=x
$ echo $y
x
$ eval echo \$$y
100
$
(You need to escape the first $ in the eval line because otherwise the first evaluation will replace "$$" with the current pid)
If you're only concerned with bash, KennyTM's method is probably best.
In ksh 93 (I don't know whether this works in ksh 88):
$ x=102; typeset -n y=x
$ echo $x
102
$ echo $y
102
$ echo ${!y}
x
Confusingly, the last two commands do the opposite of what they do in Bash (which doesn't need to flag the variable using typeset).

Resources