Shell script can not pass file data to shell input - linux

cal April 2012 | cat > t | cat < t | more
Why does it showing nothing? Why isn't it showing
April 2012
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

| (anonymous pipe) connects stdout (1) of the first process with stdin (0) of the second. After redirecting the output to a file, there is no stdout left, so there's nothing to pipe. Also, cat | cat < file does not really make sense, it gets two inputs connected to stdin (at least with bash, redirection comes later and "wins": echo uiae | cat <somefile will output the content of somefile)
If you want to display output of a command and, at the same time, write it to the file, use the tee binary. It writes to a file, but still writes to stdout
cal April 2012 | tee t | more
cat t # content of the above `cal` command

Because that first cat > t sends all its output to a file called t, leaving no more for the pipeline.
If your intent is to send it to a file and through more to the terminal, just use:
cal April 2012 | tee t | more
This | cat < t construct is very strange and I'm not even sure if it would work. It's trying to connect two totally different things to the standard input of cat and certainly unnecessary.

this works for me if there's no existing file named t in the current directory. I'm using bash on Ubuntu Oneiric.
$ cal April 2012 | cat > t | cat < t | more
April 2012
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
$ cal April 2012 | cat > t | cat < t | more
$ rm t
$ cal April 2012 | cat > t | cat < t | more
April 2012
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

Related

Parsing cal output in POSIX compliant shell script by read command

I am trying to write a POSIX compliant script, which will print all months in specified year $3, that have day in $1 (for example Mo, Tu,...) on a same date as $2 (1,2,3,...).
Example:
Input: ./task1.sh Tu 5 2006
Output:
September 2006
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30
December 2006
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
I have written this script:
#!/bin/sh
year=$3
dayInMonth=$2
dayInWeek=$1
index=1
while expr $index '!=' 13 >/dev/null; do
cal -m $index $year| tail -n +2| while read Mo Tu We Th Fr Sa Su ; do
eval theDay='$'$dayInWeek
if [ "$theDay" = "$dayInMonth" ]; then
cal -m $index $year;
fi
done
index=$(expr $index + 1)
done
But there is a problem with reading of third line of cal output. In these lines numbers of days usually don't start at Mo place. How can I parse third line of cal output so the numbers in $Mo, $Tu, $We,... are always correct?
Update: You've added the requirement for a posix conform solution. date -d as used in my answer is not POSIX conform. I'll keep the answer for those who are using GNU/Linux.
Btw, the following command gives you posixly correct the day of week offset of Jan 5, 2006:
cal 01 2006 | awk -v d=5 'NR>2{for(i=1;i<NF;i++){if($i==d){print i;exit}}}'
You need to tinker a little shell script around that.
I would use the date command, like this:
#!/bin/bash
dayofweek="${1}"
day="${2}"
year="${3}"
for m in {01..12} ; do
date=$(LANG=C date -d "${year}-${m}-${day}" +'%a %B')
read d m <<< "${date}"
[ "${d}" = "${dayofweek}" ] && echo "${m}"
done
Results:
$ bash script.sh Thu 05 2006
January
October
It's easier to check dates with the command date.
for month in {1..12}; do
if [[ $(date -d $(printf "%s-%2.2d-%2.2d" "$year" "$month" "$day") "+%a") == "Tue" ]]; then
cal -m $month $year;
fi
done
The script loops over the 12 months and generate a date based on year and day. The date command outputs the day of the in a 3 letters format with +%a.
If you want the day of week in number format, use +%u and == 2 in the if statement.

Linux Grouping and Counting Files by attribute

I am trying to return a list of the months that files were created using the following code.
ls -l|awk '{A[$6":"]++}END{for (i in A){print i" "A[i]}}'
I am using the below code to validate each output.
ls -la | grep -c "Jan"
However as you can see from my output:
: 1
Jan: 19
Feb: 11
Mar: 28
Apr: 10
May: 14
Jun: 24
Jul: 4
Aug: 16
Sep: 10
Oct: 30
Nov: 4
Dec: 1
Output of ls|grep
I end up with 1 record showing no date. Also both January and December are short by 1. Can anyone assist?
You could do it this way using awk and sort
$ ls -l | awk '$6!=""{m[$6]++}END{for(i in m){printf "%s : %s%s",i,m[i],ORS }}' | sort -k1M
Jan : 7
Mar : 1
Apr : 8
Aug : 2
The problem comes with the first line of ls -l which doesn't contain a month field

Command redirection

How can I redirect output of a Linux commands along with the command to a file?
Like ls -l >> test.txt will only redirect the output and not the command also to the file. I am a novice
Using script command you can log, which will save the entire terminal session until you exit the program, for example
Commands executed
akshay#db-3325:/tmp$ script test.log
Script started, file is test.log
akshay#db-3325:/tmp$ cal
March 2017
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
akshay#db-3325:/tmp$ uptime
23:43:16 up 4:31, 1 user, load average: 0.78, 0.67, 0.44
akshay#db-3325:/tmp$ w
23:43:18 up 4:31, 1 user, load average: 0.72, 0.66, 0.43
USER TTY FROM LOGIN# IDLE JCPU PCPU WHAT
akshay tty7 :0 19:12 4:31m 7:49 0.28s /sbin/upstart --user
akshay#db-3325:/tmp$ whoami
akshay
akshay#db-3325:/tmp$ echo 'something'
something
akshay#db-3325:/tmp$ exit
exit
Script done, file is test.log
This is what logged :
akshay#db-3325:/tmp$ cat test.log
Script started on Thursday 16 March 2017 11:43:06 PM IST
akshay#db-3325:/tmp$ cal
March 2017
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
akshay#db-3325:/tmp$ uptime
23:43:16 up 4:31, 1 user, load average: 0.78, 0.67, 0.44
akshay#db-3325:/tmp$ w
23:43:18 up 4:31, 1 user, load average: 0.72, 0.66, 0.43
USER TTY FROM LOGIN# IDLE JCPU PCPU WHAT
akshay tty7 :0 19:12 4:31m 7:49 0.28s /sbin/upstart --user
akshay#db-3325:/tmp$ whoami
akshay
akshay#db-3325:/tmp$ echo 'something'
something
akshay#db-3325:/tmp$ exit
exit
Script done on Thursday 16 March 2017 11:43:38 PM IST
If you don't want to use script utility there are some alternatives, using set -x bash feature:
$ exec 2>log; set -x; pwd >&2;set +x;exec 2>&1;cat log
+ pwd
/home/gv/Desktop/PythonTests
+ set +x
Or you can also use a custom function without tricky redirections:
$ function logme { a="$#"; echo $a >log ; "$#" >>log 2>&1;cat log;return; }
$ logme pwd
pwd
/home/gv/Desktop/PythonTests
PS: You can offcourse send the output to another file , i.e logme pwd >>general log
Finally, you can build a custom script like bellow, making use also of set -v (verbose):
exec 3>&2 2>log 4<log
set -vx
"$#" >&2
set +vx
cat <&4 >&1 # Optionally combine with |tail -n +2 |head -n -2
#The cat is used to display in command line what is logged inside the file.

How to set an arbitrary seed to the --random-sort option of Linux SORT?

In man page of SORT, it says you can set a random source like:
$ sort some.txt --random-sort --random-source=/dev/urandom
I want to an standard output text to the source like:
$ sort some.txt --random-sort --random-source=`date +"%m%d%H%M"`
But this only says:
open failed: 11021103: No such file or directory
How can I do this?
Here's a simple python script that takes a seed and outputs random bytes:
> cat rand_bits.py
import random
import sys
if len(sys.argv) > 1:
rng = random.Random(int(sys.argv[-1]))
else:
rng = random.Random(0xBA5EBA11)
try:
while True:
sys.stdout.write(chr(rng.getrandbits(8)))
except (IOError, KeyboardInterrupt):
pass
sys.stdout.close()
You can just feed those bytes straight into sort:
> sort <(seq 25) -R --random-source=<(python rand_bits.py 5)
8
2
4
7
10
19
17
11
3
20
14
18
1
16
25
12
5
21
24
23
22
9
15
13
6
By the way, the input can be any file, but the file better be long enough!
> sort <(seq 25) -R --random-source=<(date +"%m%d%H%M")
sort: /dev/fd/12: end of file
> sort <(seq 25) -R --random-source=/dev/sda1
3
13
24
5
10
16
4
17
12
18
14
2
6
15
23
21
19
11
9
1
20
25
22
8
7

Using variables with sed

I'm trying to delete a part of a file using sed in Linux (Ubuntu). Specifically, I want to delete the first lines of a log file until the first occurrence of the current system date (using the pattern '10 Jan 13').
So, I store the date in a variable
root#server:/# VAR_DATE=`date -R | cut -c6-11`
And after that, I use sed
root#server:/# cat log_file.txt | sed -n -e '/$VAR_DATE/,$p'
But it doesn't work. I've tried a lot of combinations with the same result:
root#server:/# cat log_file.txt | sed -n -e '/"$VAR_DATE"/,$p'
root#server:/# cat log_file.txt | sed -n -e '/"${VAR_DATE}"/,$p'
root#server:/# cat log_file.txt | sed -n -e "/$VAR_DATE/,$p"
What I'm doing wrong?
Use double quotes so the variable $vardate gets expanded by the shell and escape the last $ so it's not expanded by the shell sed -n "/$vardate/,\$p" file:
$ cat file
6 Jan 13
7 Jan 13
8 Jan 13
9 Jan 13
10 Jan 13
11 Jan 13
12 Jan 13
13 Jan 13
$ vardate="10 Jan 13"
$ sed -n "/$vardate/,\$p" file
10 Jan 13
11 Jan 13
12 Jan 13
13 Jan 13

Resources