Wednesday, 17 June 2015

grep

Remove files without match using bash grep :
------------------------------------------------------------------

 I have already discussed in one of my earlier post how we can find the files which do not contain a particular pattern (string).

As I mentioned in that particular post:

"Well, when I first heard of this requirement; I just thought I will use "grep -vl pattern" piped with xargs to a regular find command (i.e. "find . -type f | xargs grep -vl pattern")
(-l : Suppress normal output; instead print the name of each input file from which output would normally have been printed.)

Then I realized that "grep -v" only works in line level, i.e. a file which contain the "pattern" in some line(s) may also contain some line(s) which "do not" contain that "pattern"; so as a whole that file will also be in the list of files which "do not" contain the "pattern" even though it contains the pattern."

From GREP(1) man pages:

-L, --files-without-match
Suppress normal output; instead print the name of each input file from which no out-put would normally have been printed. The scanning will stop on the first match.

-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected. Also see the -s or --no-messages option.

So to remove or delete the files which do not contain the pattern or string "hello"

$ find . -type f -exec grep -L "hello" {} \; | xargs rm

or


$ find . -type f \! -exec grep -q "hello" {} \; -print | xargs rm


Remove files without match using bash grep:
-----------------------------------------------------------------

I have already discussed in one of my earlier post how we can find the files which do not contain a particular pattern (string).

As I mentioned in that particular post:

"Well, when I first heard of this requirement; I just thought I will use "grep -vl pattern" piped with xargs to a regular find command (i.e. "find . -type f | xargs grep -vl pattern")
(-l : Suppress normal output; instead print the name of each input file from which output would normally have been printed.)

Then I realized that "grep -v" only works in line level, i.e. a file which contain the "pattern" in some line(s) may also contain some line(s) which "do not" contain that "pattern"; so as a whole that file will also be in the list of files which "do not" contain the "pattern" even though it contains the pattern."

From GREP(1) man pages:

-L, --files-without-match
Suppress normal output; instead print the name of each input file from which no out-put would normally have been printed. The scanning will stop on the first match.

-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected. Also see the -s or --no-messages option.

So to remove or delete the files which do not contain the pattern or string "hello"

$ find . -type f -exec grep -L "hello" {} \; | xargs rm

or

$ find . -type f \! -exec grep -q "hello" {} \; -print | xargs rm

screen

Beginning of line in UNIX screen session:
-----------------------------------------------------------

As you know every "screen" command begins with "Ctrl-a", then how to go to beginning of the line when you are working under a "screen" UNIX session (which we achieve by "Ctrl-a" in a normal UNIX session) ? 

Solution:
Under a "screen" UNIX session you can do "Ctrl-a a" to move to the beginning of the line. 


So in order to print ^A (hex: \x01) in a UNIX "screen" session, usual  "Ctrl-v Ctrl-a" will not work, you will type "Ctrl-v Ctrl-a a" to print ^A.


Thursday, 4 June 2015

sed command examples

Insert, append, change lines using sed :
--------------------------------------------------------

Input file:
$ cat order789.txt
title:
PO for vessel unit1 const.
items:
fan:F34539
tube:L1245
driller:M4545
Description:
PO signed and verified by factory manager S K Lp
Date: Fri Jan 2 17:26:44 UTC 2009
Author: M Kumar

# Add the line "heater:M21789" after the line "items"
$ sed '
/items/ a\
heater:M21789
' order789.txt

Output:
title:
PO for vessel unit1 const.
items:
heater:M21789
fan:F34539
tube:L1245
driller:M4545
Description:
PO signed and verified by factory manager S K Lp
Date: Fri Jan 2 17:26:44 UTC 2009
Author: M Kumar

# Add the line "heater:M21789" after line number 3 (output would be same as above)
$ sed '
3 a\
heater:M21789
' order789.txt

# Insert a line "heater:M21789" before the line beginning with "fan"
$ sed '
/^fan/ i\
heater:M21789
' order789.txt

Output:
title:
PO for vessel unit1 const.
items:
heater:M21789
fan:F34539
tube:L1245
driller:M4545
Description:
PO signed and verified by factory manager S K Lp
Date: Fri Jan 2 17:26:44 UTC 2009
Author: M Kumar

# We can insert or add more than one line also.
$ sed '
4 i\
heater:M21789\
newitem:YYYYY
' order789.txt

Output:
title:
PO for vessel unit1 const.
items:
heater:M21789
newitem:YYYYY
fan:F34539
tube:L1245
driller:M4545
Description:
PO signed and verified by factory manager S K Lp
Date: Fri Jan 2 17:26:44 UTC 2009
Author: M Kumar


# One can change a line as well, e.g. to change the line beginning with "fan" with the line "BigFAN:F5757"
$ sed '
/^fan/ c\
BigFAN:F5757
' order789.txt

Output:
title:
PO for vessel unit1 const.
items:
BigFAN:F5757
tube:L1245
driller:M4545
Description:
PO signed and verified by factory manager S K Lp
Date: Fri Jan 2 17:26:44 UTC 2009
Author: M Kumar


Embedding shell command in sed - bash:
-----------------------------------------------------------

Input file:

$ cat file.txt
port:9903
os-version:VERSION
codename:hardy
status:active

The content of '/etc/lsb-release' file on my ubuntu desktop:

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=8.04
DISTRIB_CODENAME=hardy
DISTRIB_DESCRIPTION="Ubuntu 8.04.3 LTS"

Lets extract the 'DISTRIB_RELEASE' version from the above file:

$ awk -F '=' '/DISTRIB_RELEASE/ {print $2}' /etc/lsb-release
8.04

Required: Replace the text 'VERSION' in the input file 'file.txt' with the output of the above command (i.e. 'DISTRIB_RELEASE' version)

First way of doing this:

$ myvar=$(awk '/DISTRIB_RELEASE/ {print $2}' FS=\= /etc/lsb-release)
$ sed "s/VERSION/$myvar/g" file.txt

Output

port:9903
os-version:8.04
codename:hardy
status:active

** Important to see that we have used double quotes (instead of regular single quote) in the above sed statement. Using single quote is not going to expand the content of the variable 'myvar'.

Other ways :

$ sed "s/VERSION/`awk -F '=' '/DISTRIB_RELEASE/ {print $2}' /etc/lsb-release`/g" file.txt

Which is same as:

$ sed "s/VERSION/$(awk -F '=' '/DISTRIB_RELEASE/ {print $2}' /etc/lsb-release)/g" file.txt

And another way of quoting (This will allow you to use the single quote with sed statement):

$ sed 's/VERSION/'"$(awk -F '=' '/DISTRIB_RELEASE/ {print $2}' /etc/lsb-release)"'/g' file.txt

same as

$ sed 's/VERSION/'"`awk -F '=' '/DISTRIB_RELEASE/ {print $2}' /etc/lsb-release`"'/g' file.txt


Extract range of lines using sed awk bash:
-------------------------------------------------------------

Below are few different ways to print or extract a section of a file based on line numbers.

Lets try to extract lines between line number 27 and line number 99 of input file 'file.txt'

Using sed editor:

$ sed -n '27,99 p' file.txt > /tmp/file1

Which is same as:

$ sed '27,99 !d' file.txt > /tmp/file2

Awk alternative : you can make use of awk NR variable

$ awk 'NR >= 27 && NR <= 99' file.txt > /tmp/file3

Using Linux/UNIX 'head' and 'tail' command:

$ head -99 file.txt | tail -73 > /tmp/file4

Which is basically:

$ head -99 file.txt | tail -$(((99-27)+1)) > /tmp/file5

In vi editor, we can use the following command in ex mode (open the main file 'file.txt' in vi):

:27,99 w! /tmp/file6

i.e. Write lines between line number 27 and line number 99 of main file 'file.txt' to file '/tmp/file6'

Perl alternative would be:

$ perl -ne 'print if 27..99' file.txt > /tmp/file7

And the solution using python:

$ python
Python 2.5.2 (r252:60911, Jul 22 2009, 15:35:03)
[GCC 4.2.4 (Ubuntu 4.2.4-1ubuntu3)] on linux2

>>> fp = open("/tmp/file8","w")
>>> for i,line in enumerate(open("file.txt")):
...     if i >= 26 and i < 99 :
...             fp.write(line)
...
>>>

So the contents of all the output files produced (i.e /tmp/file[1-8]) will be the same (i.e. line number 27 to line number 99 of 'file.txt')


Sed to replace a part of file : bash scripting :
-----------------------------------------------------------

$ cat idfile
id='1' dsadsad adsad
id='31' dsadsad adsad 32432
id='231' dsadsad adsad 3234
id='123' 2332 dsadsad adsad
id='124' 2332


Output required: 
Subtract 1 from the id value from each line of the above file. i.e. final output should like this:

id='0' dsadsad adsad
id='30' dsadsad adsad 32432
id='230' dsadsad adsad 3234
id='122' 2332 dsadsad adsad
id='123' 2332

The script:

$ cat idfile | while read line
> do
> R=`echo $line | sed "s/id='\([0-9].*\)'.*/\1/"`
> ((R-=1))
> echo $line | sed "s/\(id='\)\([0-9].*\)\('.*\)/\1$R\3/"
> done

Note:
If
((R-=1))
complians, then use
R=`expr $R - 1`


Some references:

$ echo "id='1' dsadsad adsad" | sed "s/\(id='\)\([0-9].*\)\('.*\)/\1/"
id='

$ echo "id='1' dsadsad adsad" | sed "s/\(id='\)\([0-9].*\)\('.*\)/\2/"
1

$ echo "id='1' dsadsad adsad" | sed "s/\(id='\)\([0-9].*\)\('.*\)/\3/"
' dsadsad adsad


Merge previous line using sed :
----------------------------------------------

Input file:

$ cat file.txt
1 AA 2
2 BB 3
3 DD 4
5 CC 12
7 ZZ 12

Required output:
If I search for line with first field as "3" , th line with first field 3 should be merged with the previous line to it.
i.e

1 AA 2
2 BB 3 3 DD 4
5 CC 12
7 ZZ 12


Find the character immediately after a word - sed:
------------------------------------------------------------------------

$ echo "unix:bash/scripting:12"
unix:bash/scripting:12


Purpose: find the character after the word "bash"


$ echo "unix:bash/scripting:12" | sed 's/.*bash\(.\).*/\1/'


Delete one or more space using sed- bash:
------------------------------------------------------------

Input file: 
$ cat test.txt
one two three       four
1   2 3  4
i     ii   iii           iv

Sed code to delete/remove one or more space from a file. (Basically combine one or more space to a single space)

$ sed 's/[ \t]\+/ /g' test.txt > test.txt.1

$ cat test.txt.1
one two three four
1 2 3 4
i ii iii iv


Remove or replace newlines using sed,awk,tr - BASH:
----------------------------------------------------------------------------

$ cat week.txt
Su
Mo
Tu
We
Th
Fr
Sa


Output Required:

- a) Remove the newlines. i.e. required output:
SuMoTuWeThFrSa

- b) Replace the newlines with "|", i.e.
Su|Mo|Tu|We|Th|Fr|Sa


Remove/Replace newlines with sed

a)
$ sed -e :a -e '$!N;s/\n//;ta' week.txt
SuMoTuWeThFrSa

b)
$ sed -e :a -e '$!N;s/\n/|/;ta' week.txt
Su|Mo|Tu|We|Th|Fr|Sa


One more way of doing. But not suitable for files with large number of records, as you see the number of N's is just 1 less than number of lines in the file.


a)
$ sed 'N;N;N;N;N;N;s/\n//g' week.txt
SuMoTuWeThFrSa

b)
$ sed 'N;N;N;N;N;N;s/\n/|/g' week.txt
Su|Mo|Tu|We|Th|Fr|Sa


Remove/Replace newlines with awk

a)
$ awk '{printf "%s",$0} END {print ""}' week.txt
SuMoTuWeThFrSa

b)
$ awk '{printf "%s|",$0} END {print ""}' week.txt
Su|Mo|Tu|We|Th|Fr|Sa|

So we need to remove the last "|" in the above output.

$ awk '{printf "%s|",$0} END {print ""}' week.txt | awk '{sub(/\|$/,"");print}'
Su|Mo|Tu|We|Th|Fr|Sa


Remove/Replace newlines with tr

a)
$ tr -d '\n' < week.txt
SuMoTuWeThFrSa

b)
$ tr '\n' '|' < week.txt
Su|Mo|Tu|We|Th|Fr|Sa|

Similarly we need to remove the last "|" from the above output:
$ tr '\n' '|' < week.txt | sed 's/|$//'
Su|Mo|Tu|We|Th|Fr|Sa

Tuesday, 2 June 2015

split

Split Command:
---------------------------

-d , numeric file name
-a, number of character in the file name.
  • 0 at the end is file prefix

split -5100 -d -a6  187557-post1.13134_12.1367607900 0

output:
-rw-r--r-- 1 root root 5.1M Sep  1 07:01 0000003
-rw-r--r-- 1 root root 4.7M Sep  1 07:01 0000002

-rw-r--r-- 1 root root 4.8M Sep  1 07:01 0000001

Monday, 1 June 2015

xargs

Unix xargs parallel execution of commands:
--------------------------------------------------------------

Xargs has option that allows you to take advantage of multiple cores in your machine. Its -P option which allows xargs to invoke the specified command multiple times in parallel. From XARGS(1) man page: 
-P max-procs
   Run up to max-procs processes at a time; the default is 1.  If max-procs is 0, xargs will run as many processes as possible at a time.   Use  the  -n  option
   with -P; otherwise chances are that only one exec will be done.

-n max-args
    Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see the -s option) is exceeded, unless the  -x  
    option is given, in which case xargs will exit.

-i[replace-str]
    This option is a synonym for -Ireplace-str if replace-str is specified, and for -I{} otherwise.  This option is deprecated; use -I instead.
Let me try to give one example where we can make use of this parallel option avaiable on xargs. e.g. I got these 8 log files (each one is of 1.5G size) for which I have to run a script named count_pipeline.sh which does some calculation around the log lines in the log file. 
$ ls -1 *.out
log1.out
log2.out
log3.out
log4.out
log5.out
log6.out
log7.out
log8.out
The script count_pipeline.sh takes nearly 20 seconds for a single log file. e.g. 
$ time ./count_pipeline.sh log1.out

real 0m20.509s
user 0m20.967s
sys 0m0.467s
If we have to run count_pipeline.sh for each of the 8 log files one after the other, total time needed: 
$ time ls *.out | xargs -i ./count_pipeline.sh {}           

real 2m45.862s
user 2m48.152s
sys 0m5.358s
Running with 4 parallel processes at a time (I am having a machine which is having 4 CPU cores): 
$ time ls *.out | xargs -i -P4 ./count_pipeline.sh {} 

real 0m44.764s
user 2m55.020s
sys 0m6.224s
We saved time ! Isn't this useful ? You can also use -n1 option instead of the -i option that I am using above. -n1 passes one arg a time to the run comamnd (instead of the xargs default of passing all args). 
$ time ls *.out | xargs -n1 -P4 ./count_pipeline.sh

real 0m43.229s
user 2m56.718s

sys 0m6.353s

Thursday, 28 May 2015

diff

Diff remote files using ssh in Linux:
-----------------------------------------------------

I have already discussed how we can edit a remote file using vi and scp in one of my previous post ; today we will see how we can find or show differences between a local file and a remote file using ssh.

Suppose we have to find the differences between local file "/tmp/filepurge.sh.old" and remote file "/root/scripts/filepurge.sh" located in remote host 172.21.16.11.

This is how can do it:

$ ssh root@172.21.16.11 "cat ~/scripts/filepurge.sh" | diff - /tmp/filepurge.sh.old

And using vimdiff:

$ vimdiff scp://root@172.21.16.11//root/scripts/filepurge.sh /tmp/filepurge.sh.old


** We would need ssh to work using public key authentication so that we can do remote commands execution without being prompted for passwords.

Wednesday, 20 May 2015

cat

Bash cat command space issue explained:
-----------------------------------------------------------

Input file contains some 4 student names like this:

$ cat file.txt
Alex C M
Peter S
Dhiren K
Prahlad G N

Required: I was trying to produce the following output:

1) Alex C M [3]
2) Peter S [2]
3) Dhiren K [2]
4) Prahlad G N [3]

i.e. a serial number, Name of the student, number of words in his name.
Lets try with bash for loop like this:

$ cat lp1.sh
#!/bin/sh

c=0
for line in $(cat file.txt)
    do
        ((c+=1))
        numfields=$(echo $line | awk '{print NF}')
        echo "$c) $line [$numfields]"
done

And the output it produced !

$ ./lp1.sh
1) Alex [1]
2) C [1]
3) M [1]
4) Peter [1]
5) S [1]
6) Dhiren [1]
7) K [1]
8) Prahlad [1]
9) G [1]
10) N [1]

So what went wrong ?
I tried echo "$line" as well, same output.

In the above example, we need to take care of the Bash IFS environmental variable. From Bash man page:

IFS:
The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read built in command.
The default value is
<space><tab><newline>.

And since the lines in the input file got lines with spaces in between, above script is behaving in that way.
We can temporarily change the IFS in the shell script like this:

$ cat lp3.sh
#!/bin/sh

OLD_IFS=$IFS
IFS=$'\n'
c=0
for line in $(cat file.txt)
    do
        ((c+=1))
        numfields=$(echo $line | awk '{print NF}')
        echo "$c) $line [$numfields]"
done
IFS=$OLD_IFS

Output:

$ ./lp3.sh
1) Alex C M [3]
2) Peter S [2]
3) Dhiren K [2]
4) Prahlad G N [3]

Bash 'while loop' used in the below way also works without changing the IFS:

$ cat lp2.sh
#!/bin/sh

c=0
while read line
    do
        ((c+=1))
        numfields=$(echo $line | awk '{print NF}')
        echo "$c) $line [$numfields]"
done < "file.txt"

Output:

$ ./lp2.sh
1) Alex C M [3]
2) Peter S [2]
3) Dhiren K [2]
4) Prahlad G N [3]

Note: The above example is taken mainly to show the use of Bash IFS variable. Using awk, the above can be done easily like this:

$ awk '{print NR")",$0,"["NF"]"}' file.txt
$ awk '{++c}{print c")",$0,"["NF"]"}’ file.txt


Wednesday, 13 May 2015

less

Print line number with unix less command :
--------------------------------------------------------------

Less command introduction:
As you know Unix less command writes the contents of a file onto the screen a page at a time and this is one of the utilities using which one can view the content of a file without opening it in an editor.
Press the [space-bar] if you want to see another page, and type [q] if you want to quit reading.

This is how we can print line number with Unix less command.

1)
From man page of CAT(1):
       -n, --number
              number all output lines

So,
$ cat -n file.txt | less

will print the line number in-front of each line.

2)
From man page of LESS(1):
       -N or --LINE-NUMBERS
              Causes a line number to be displayed at the beginning of each line in the display.

So,
$ less -N file.txt

will do the same as 1) above.

3) One can set the following to print line number with less by default:

$ export LESS='-RS#3NM~g

Thursday, 7 May 2015

exec,find

Execute multiple commands with exec and find:
--------------------------------------------------------------------

Is it possible to execute multiple commands using exec on the Linux/UNIX find command output ?

The answer is "yes". Each -exec action is to be associated with a escaped semi-colon (\;)

e.g.

I had to find files named "1251936000.log" and then need to perform two actions on it:

- Count number of lines in the file.
- Do a "ls -l" listing of the file.

And the find command I wrote with exec:

$ find . -name 1251936000.log -exec wc -l {} \; -exec ls -l {} \;

Output:

   6924 ./lv1/1251936000.log
-rw-r--r-- 1 root root 977264 Sep  4 00:17 ./lv1/1251936000.log


List empty directories using find in bash:
---------------------------------------------------------------

Linux command find gives an option called "empty" using which we can list empty regular files or empty directories.

e.g.

To list all the empty directories

$ find . -type d -empty

Output: 

./bdb/prac
./sim/old/data
./prac/testd


Linux find command and logical operators :
---------------------------------------------------------------------

Some of the example to show how we can use logical operators with linux find command.

To find any file whose name ends with either 'sh' or 'pl'

$ find . -type f \( -iname "*.sh" -or -iname "*.pl" \)

To find .txt files that are writeable by "others"

$ find . -type f \( -iname "*.txt" -and -perm -o=w \)

To find .txt files but exclude the ones which are writeable by "others"

$ find . -type f \( -iname "*.txt" ! -perm -o=w \)

or

some find versions support "-not" as well

$ find . -type f \( -iname "*.txt" -not -perm -o=w \)

Remember: The parentheses must be escaped with a backslash, "\(" and "\)", to prevent them from being interpreted as special shell characters.

Wednesday, 6 May 2015

wait

Bash wait command:
-----------------------------------

wait command stop script execution until all jobs running in background have terminated, or until the job number or process id specified as an option terminates.
It returns the exit status of waited-for command.

wait can take the job-id or the process number. i.e.

wait%1 or wait $PID

_________
wait ${!}
_________

wait ${!} means "to wait till the last background process is completed" ($! being the PID of the last background process)

________________________
An example on wait command.
________________________

Suppose
- you have a script called sort_db.sh which sorts some data files and takes a lot of time to complete(you definitely want it to run as background process in your script)
- One more script called bkptmp.sh, which does some job of backing up some tmp files(nowhere related to the above sort_db.sh)
- You have to perform some tasks in your script after sort_db.sh and bkptmp.sh complete their individual tasks (Note: both the .sh should be completed before you perform the said operation)


$ cat waittest.sh
#!/bin/sh

./sort_db.sh &
echo "1st Line"

./bkptmp.sh &
echo "2dn line"

wait
echo "Some operation will follow this"
...
...


In such situations the "bash wait command" is useful. It will wait till sort_db.sh and bkptmp.sh get complete their execution.


You might be thinking, we could have run sort_db.sh and bkptmp.sh in foreground, so that the execution of the operation will follow them. The problem is that you don't want to wait for sort_db.sh to complete for bkptmp.sh to start (I told earlier, they are not dependent). So using wait, the time sort_db.sh gets completed, we will be done(or almost done) with bkptmp.sh.

Thursday, 23 April 2015

paste

Insert lines from files using Linux paste command:
------------------------------------------------------------------------

Input files:

$ cat contestant.txt
Christopher
Williams
Darwin
Ajay
Brain
Amay
Jiten
Lila

$ cat leader.txt
Mr B
Mrs C
Mrs A

Output required:

For every single line of 'leader.txt'; insert 3 lines from file 'contestant.txt'; so that the output looks like this:

Mr B
Christopher
Williams
Darwin
Mrs C
Ajay
Brain
Amay
Mrs A
Jiten
Lila


The step by step solution using Linux/UNIX paste command

$ cat contestant.txt | paste - - -
Output:
Christopher     Williams        Darwin
Ajay    Brain   Amay
Jiten   Lila

$ cat contestant.txt | paste - - - | paste leader.txt -
Output:
Mr B    Christopher     Williams        Darwin
Mrs C   Ajay    Brain   Amay
Mrs A   Jiten   Lila

$ cat contestant.txt | paste - - - |paste leader.txt - |tr "\t" "\n"
Output:
Mr B
Christopher
Williams
Darwin
Mrs C
Ajay
Brain
Amay
Mrs A
Jiten

Lila

Wednesday, 15 April 2015

du,df

Directory size excluding sub-directories - Linux:
---------------------------------------------------------------------

Directory '/home/user/work/demo/' contains a few regular files and two directories say "part2"(size=41236 KB) and "libs"(size=20620 KB).

$ du ~/work/demo/
41236   /home/user/work/demo/part2
20620   /home/user/work/demo/libs
87640   /home/user/work/demo/

From Linux/UNIX DU(1) command man page:

-s, --summarize
display only a total for each argument.

So, the following command is going to display the total size of the directory '/home/user/work/demo/'

$ du -s ~/work/demo/
87640   /home/user/work/demo/

Now, if you need to find the size of the '/home/user/work/demo/' directory excluding the size of the sub-directories, there is a command line option with DU(1):

-S, --separate-dirs
do not include size of sub-directories

So

$ du -S ~/work/demo/
41236   /home/user/work/demo/part2
20620   /home/user/work/demo/libs
25784   /home/user/work/demo/

Now,

$ du -S --max-depth=0 ~/work/demo/
25784   /home/user/work/demo/

or

$ du -S ~/work/demo/ | awk 'END {print}'

25784   /home/user/work/demo/

awk

please refer my another blog : Top examples of awk command in unix

Wednesday, 11 March 2015

cp

Linux - copy file and preserve timestamp, ownership, mode:
------------------------------------------------------------------------------------------

If you want to copy files in Linux and also want to keep or preserve the original mode or timestamp or ownership (or all) , cp command gives an option (--preserve).

From cp command man pages:

--preserve[=ATTR_LIST]
preserve the specified attributes (default: mode,ownership,timestamps) and security contexts, if possible additional attributes: links, all

Lets discuss this with some small examples.

I am logged in as user 'jk'

$ id
uid=32321(jk) gid=700(staff)

The example file tre.sh is having the following details:

$ ls -l tre.sh
-rw-r--r-- 1 jk staff 476 2009-01-13 16:20 tre.sh

Lets copy tre.sh to /tmp/tre.sh

$ cp tre.sh /tmp/tre.sh

So the timestamp is changed to the present timestamp

$ ls -l /tmp/tre.sh
-rw-r--r-- 1 jk staff 476 2009-02-05 15:10 /tmp/tre.sh

Now copy using "--preserve=timestamps" option.

$ cp --preserve=timestamps tre.sh /tmp/tre.sh.1

The original timestamp is preserved here

$ ls -l /tmp/tre.sh.1
-rw-r--r-- 1 jk staff 476 2009-01-13 16:20 /tmp/tre.sh.1

Now I just switched to root user

$ id
uid=0(root) gid=0(root) groups=0(root)

Copy tre.sh to /tmp/tre.sh.2

$ cp tre.sh /tmp/tre.sh.2

Notice the ownership and timestamp of the /tmp/tre.sh.2

$ ls -l /tmp/tre.sh.2
-rw-r--r-- 1 root root 476 2009-02-05 15:13 /tmp/tre.sh.2

You can preserve the ownership like this:
$ cp --preserve=ownership tre.sh /tmp/tre.sh.4

So /tmp/tre.sh.4 is still owned by user jk" (copied by root though)
$ ls -l /tmp/tre.sh.4
-rw-r--r-- 1 jk staff 476 2009-02-05 15:14 /tmp/tre.sh.4

Also we can specify "--preserve=ownership,timestamps" and also preserve the mode(permission) of the file with "--preserve=mode"

The cp command -p option is equivalent to --preserve=mode,ownership,timestamps

I am still 'root'; now copy using -p option

$ cp -p tre.sh /tmp/tre.sh.5

All the original attributes (mode,permission,ownership) of tre.sh is preserved.

$ ls -l /tmp/tre.sh.5

-rw-r--r-- 1 jk staff 476 2009-01-13 16:20 /tmp/tre.sh.5

Saturday, 28 February 2015

vi

Remove leading white space in vi editor:
------------------------------------------------------------

Well it sounds very simple, but thought would be useful for all vi newbies :-)

This is how we can remove all leading white space(s) from all lines in vi editor:


In ex mode:


:1,$ s/^\s*//g


Delete blank lines in vi editor:
-----------------------------------------------

Simple but useful vi editor tip.

Q. How can I delete any blank lines within a file in vi editor?

Ans:
In escape mode type

:g/^$/ d

And all your blank lines should be removed !!

Other ways of removing blank lines from a file using grep, awk and sed

$ grep -v '^$' file
$ grep '.' file
$ sed '/^$/d' file
$ sed -n '/^$/!p' file
$ awk NF file
$ awk '/./‘ file

Wednesday, 18 February 2015

nohup

Run UNIX bash loop with nohup :
--------------------------------------------------

Suppose we have to run following UNIX bash shell loop construct under 'nohup'
$ for i in $(seq 20); do echo $i;./somescript.sh $i; done

Running the above loop under nohup directly will not work as 'nohup' expects a single-word command and its arguments. This is how we can run a UNIX bash shell loop construct using 'nohup'.
$ nohup sh -c 'for i in $(seq 20); do echo $i;./somescript.sh $i; done' &

From man page of 'sh'
-c               Read commands from the command_string operand instead of from the
standard input.  Special parameter 0 will be set from the command_name operand

and the positional parameters ($1, $2, etc.)  set from the remaining argument operands.

Wednesday, 4 February 2015

bash

Beginning of line in unix screen session:
---------------------------------------------------------

As you know every "screen" command begins with "Ctrl-a", then how to go to beginning of the line when you are working under a "screen" UNIX session (which we achieve by "Ctrl-a" in a normal UNIX session) ? 

Solution:
Under a "screen" UNIX session you can do "Ctrl-a a" to move to the beginning of the line. 


So in order to print ^A (hex: \x01) in a UNIX "screen" session, usual  "Ctrl-v Ctrl-a" will not work, you will type "Ctrl-v Ctrl-a a" to print ^A.


UNIX time command output redirect to file:
---------------------------------------------------------------

$ time ./prog.sh 
Initiating merge
Merge completed

real 0m8.803s
user 0m0.010s
sys 0m0.000s
#Trying to redirect the output to a file 
$ time ./prog.sh > out.txt

real 0m8.804s
user 0m0.020s
sys 0m0.000s
#out.txt content: 
$ cat out.txt 
Initiating merge
Merge completed
#So it only redirected the STDOUT of the script executed, but the 'time' command outut is not redirected. 
#This is becuase the command time sends it's output to STDERR (instead of STDOUT)
#To capture output of 'time' command: 
$ { time ./prog.sh ; } 2> out.txt
Initiating merge
Merge completed
#Now out.txt content: 
$ cat out.txt 

real 0m8.303s
user 0m0.010s
sys 0m0.000s
#And to capture output of script as well as time command: 
$ { time ./prog.sh ; } &> out.txt
#'out.txt' now has both the outputs. 
$ cat out.txt 
Initiating merge
Merge completed

real 0m8.303s
user 0m0.020s
sys 0m0.000s
there's two types of time command available:
1) Shell's in-build time: Gives only scheduler information
2) /usr/bin/time: Gives more information, also allows formatting the output

The second (/usr/bin/time) one accepts output redirection without code block: 
$ /usr/bin/time ./prog.sh &> newout.txt

$ cat newout.txt 
Initiating merge
Merge completed
0.00user 0.01system 0:08.30elapsed 0%CPU (0avgtext+0avgdata 5728maxresident)k

0inputs+8outputs (0major+719minor)pagefaults 0swaps


Bash insert newline after every 3 lines:
--------------------------------------------------------

-----------
Input file:
-----------
$ cat file.txt
FR 24
AA 33
EE 34
EE 46
BE 30
AA 31
DE 90
AL 10
AA 50
FR 67
EE 94
AA 80
NK 80

---------
Required:
---------
Insert a newline after every 3 lines of the above file. i.e. required output:
FR 24
AA 33
EE 34

EE 46
BE 30
AA 31

DE 90
AL 10
AA 50

FR 67
EE 94
AA 80

NK 80

----------
Solutions:
----------
a) Using awk:
$ awk '!( NR % 3 ) {$0 = $0"\n"} 1' file.txt

Which is same as:
$ awk '!( NR % 3 ) {$0 = $0"\n"} {print}' file.txt

b) A UNIX bash script to achieve this:
#!/bin/sh
c=0
while read line
    do
    ((s=c%3))
    if [ "$s" -eq 0 ]; then
        echo -e "\n$line"
    else
        echo "$line"
    fi
    ((c=c+1))
done < file.txt | sed '1d'

Following if-else block

    if [ "$s" -eq 0 ]; then
        echo -e "\n$line"
    else
        echo "$line"
    fi

can also be written as:
[ "$s" -eq 0 ] && echo -e "\n$line" || echo "$line"


Unix Bash script - Check if integer or not:
-------------------------------------------------------------

Please find below two UNIX bash scripts to test if the entered input is integer or not. Request all to suggest if any other alternatives to check this. Thanks in advance.
1) Script 1:

$ cat check-integer.sh
#!/bin/sh
#Check if input is integer or not

[ -z $1 ] && echo "No input, exiting .." && exit
[[ $1 = *[![:digit:]]* ]]  && echo "Not Integer" || echo "Integer"
Intersection between two files:
 grep -Fx -f 51101 100002568


Bash - Concatenate with underscore in between:
--------------------------------------------------------------------

Input file:

$ cat file.txt
X 2 10
X 2 11
Y 5 12
Z 3 11
X 6 78

Required: For each of the lines of the above file, we are required to create sub directories of the following naming convention inside the directory outdir/.

i.e. for line
X 2 10
we need to create the sub directory:
outdir/b_config_X_2

The bash script I wrote:

$ cat createdir.sh
#!/bin/sh

while read line
    do
        eval $(echo "$line" | awk '{print "AID="$1";BID="$2";CID="$3}')
        echo "$AID,$BID,$CID"
        dir=b_config_$AID_$BID
        mkdir -p outdir/$dir
done < file.txt

Executing it:

$ ./createdir.sh
X,2,10
X,2,11
Y,5,12
Z,3,11
X,6,78

Lets see what got created under outdir/

$ ls -l outdir/
total 16
drwxr-xr-x 2 root root 4096 Dec 28 04:54 b_config_2
drwxr-xr-x 2 root root 4096 Dec 28 04:54 b_config_3
drwxr-xr-x 2 root root 4096 Dec 28 04:54 b_config_5
drwxr-xr-x 2 root root 4096 Dec 28 04:54 b_config_6

To make it work, I did the following change in the above script:

dir=b_config_$AID\_$BID

So the modified script:

$ cat createdir.sh
#!/bin/sh

while read line
    do
        eval $(echo "$line" | awk '{print "AID="$1";BID="$2";CID="$3}')
        echo "$AID,$BID,$CID"
        dir=b_config_$AID\_$BID
        mkdir -p outdir/$dir
done < file.txt

After execution, it created the correct sub directories:

$ ls -l outdir/
total 16
drwxr-xr-x 2 root root 4096 Dec 28 04:56 b_config_X_2
drwxr-xr-x 2 root root 4096 Dec 28 04:56 b_config_X_6
drwxr-xr-x 2 root root 4096 Dec 28 04:56 b_config_Y_5
drwxr-xr-x 2 root root 4096 Dec 28 04:56 b_config_Z_3

The other alternative to concatenate values of two variables with underscore in between is:

dir=b_config_${AID}_${BID}

What is the use of the following Awk eval function ?

eval $(echo "$line" | awk '{print "AID="$1";BID="$2";CID="$3}')

is a replacement of:

AID=$(echo $line | awk '{print $1}')
BID=$(echo $line | awk '{print $2}')
CID=$(echo $line | awk '{print $3}')

or

AID=$(echo $line | cut -d " " -f1)
BID=$(echo $line | cut -d " " -f2)
CID=$(echo $line | cut -d " " -f3)


Unix - Append 0 to single digit date:
-----------------------------------------------------

Input file file.txt has dates in month/day/year format. 
$ cat file.txt 
3/4/2013
3/10/2013
10/4/2013
12/10/2012
Required: Add prefix 0 to first and second field if its a single digit. 

Awk solution: 
$ awk 'BEGIN {FS=OFS="/"} 
    { 
 if (length($1) == 1) $1="0"$1
 if (length($2) == 1) $2="0"$2
        { print }
}' file.txt
Output: 
03/04/2013
03/10/2013
10/04/2013
12/10/2012


Sum of numbers in file - UNIX alternatives:
---------------------------------------------------------------

Input file:

$ cat /tmp/file.txt
286
255564800
609
146
671290

Required: Add (Sum) all the numbers present in the above file.

Way#1: This is supposed to be the most popular way of doing an addition of numbers present in a particular field of a file.

$ awk '{s+=$0} END {print s}' /tmp/file.txt
256237131

Way#2: Using UNIX/Linux 'paste' command and 'bc'

$ paste -sd+ /tmp/file.txt
286+255564800+609+146+671290

$ paste -sd+ /tmp/file.txt | bc
256237131

Way#3: Using UNIX/Linux 'tr' command and 'bc'

$ tr -s '\n' '+' < /tmp/file.txt
286+255564800+609+146+671290+

$ echo $(tr -s '\n' '+' < /tmp/file.txt)
286+255564800+609+146+671290+

#Since there's an extra '+' at end of above output, echo an additional '0' like this
$ echo $(tr -s '\n' '+' < /tmp/file.txt)0
286+255564800+609+146+671290+0

$ echo $(tr -s '\n' '+' < /tmp/file.txt)0 | bc
256237131

Way#4: Same as above but doing the arithmetic without using 'bc'

$ printf "%d\n" $(( $(tr -s '\n' '+' < /tmp/file.txt) 0 ))
256237131

Way#5: Using sed and 'bc'

$ sed 's/$/+/' /tmp/file.txt
286+
255564800+
609+
146+
671290+

$ echo $(sed 's/$/+/' /tmp/file.txt) 0
286+ 255564800+ 609+ 146+ 671290+ 0

$ echo $(sed 's/$/+/' /tmp/file.txt) 0 | bc
256237131

Way#6 : or a basic bash script using for loop

sum=0
for num in $(cat /tmp/file.txt)
    do
        ((sum+=num))
done
echo $sum

Way#7: Using python

>>> sum = 0
>>> lines = open("/tmp/file.txt", "r").readlines()
>>> lines
['286\n', '255564800\n', '609\n', '146\n', '671290\n']
>>> for line in lines:
...     sum+=eval(line)
...
>>> sum
256237131


Bash while loop sum issue explained :
-------------------------------------------------------

On one of my directory I had a lot of log files and I had to find the count of the total number of lines which starts with 's' (i.e. ^s).
My first approach was:

$ ls | xargs -i grep -c ^s {} | awk '{sum+=$0} END {print sum}'
190978

And I got my result. Then I thought of performing the same using bash scripting for and while loop and this is what I tried.

#!/bin/sh

sum=0
DIR=~/original
for file in $(ls $DIR)
  do
      Slines=$(grep -c ^s $DIR/$file)
      ((sum+=Slines))
      #You can also use
      #sum=$(expr $sum + $Slines)
      #sum=`expr $sum + $Slines`
done
echo $sum

Executing it:

$ ./usingfor.sh
190978

Cool, correct result.

And then I modified the above script for bash while loop:

#!/bin/sh
sum=0
DIR=~/original
ls $DIR | while read file
  do
      Slines=$(grep -c ^s $DIR/$file)
      ((sum+=Slines))
done
echo $sum

Executing it:

$ ./usingwhile.sh
0


Oops!!! what went wrong ?

In Bash shell, piping directly to bash while loop causes the bash shell to function in a sub shell.
So in the above example the scope of the 'sum' variable is limited to the sub-shell of the while loop and so the modified value of 'sum' is not reflected when we exit the loop. Value of sum is still 0 (local value) as we initialized it to 0 at the beginning of the script.

The solution of this variable scoping problem with while and direct piping will be:

Remove the direct pipe and feed the list of file names under '~/original' directory as stdin to the while loop as shown below (Basically create a temp file with the file names of the directory '~/original')

#!/bin/sh
sum=0
DIR=~/original
ls $DIR > /tmp/filelist

while read file
  do
      Slines=$(grep -c ^s $DIR/$file)
      ((sum+=Slines))
done < /tmp/filelist
echo $sum

Executing it:

$ ./usingwhile_1.sh
190978


Extract sub-string from variable in bash:
------------------------------------------------------------

Suppose:

$ mypath=/dir1/dir2/dir3/dir4

$ echo $mypath
/dir1/dir2/dir3/dir4

Now, if you need to print the parent path from the above path (i.e. print '/dir1/dir2/dir3')

$ dirname $mypath
/dir1/dir2/dir3

$ parentpath=$(dirname $mypath)

$ echo $parentpath
/dir1/dir2/dir3

Using Sub-string Removal ways in Bash shell

${string%substring}
It deletes shortest match of $substring from 'back' of $string.

$ echo ${mypath%/*}
/dir1/dir2/dir3

or

$ printf '%s\n' "${mypath%/*}"
/dir1/dir2/dir3

If you need to print the last directory name from the above mypath, here are few ways:

Using Sub-string Removal ways in Bash shell
${string##substring}
It deletes the "longest" match of $substring from 'front' of $string.

$ echo ${mypath##*/}
dir4

Another way using awk:

$ echo $mypath | awk '{print $NF}' FS=\/
dir4


Bash script to copy required files:
---------------------------------------------------

Contents of my "inputdir" is a set of files with filename like this:

$ ls -1 inputdir/
log.10.16.1253168140.txt
log.11.5.1253168345.txt
log.11.9.1253168347.txt
log.12.1.1253168347.txt
log.19.1.1253168140.txt

Directory "testcfgs" contains a set of config xmls.

$ ls -1 testcfgs/
cfg_10_16.xml
cfg_10_5.xml
cfg_11_5.xml
cfg_11_9.xml
cfg_12_1.xml
cfg_19_1.xml
cfg_19_2.xml
cfg_91_9.xml

Required:

For each file of name "log.X.Y.timestamp.txt" in "inputdir", copy the corresponding "cfg_X_Y.xml" config file from "testcfgs" to a directory say "requiredcfgs".

A simple practical bash one liner script:

$ for filename in $(ls -1 inputdir/)
> do
> X=$(echo "$filename" | cut -d"." -f2)
> Y=$(echo "$filename" | cut -d"." -f3)
> cp testcfgs/cfg_$X\_$Y.xml requiredcfgs/
> done

The two lines above for finding X and Y value can be replaced by a single line using 'eval with awk', like this:

$ for filename in $(ls -1 inputdir/)
> do
> eval $(echo "$filename" | awk -F "." '{print "X="$2";Y="$3}')
> cp testcfgs/cfg_$X\_$Y.xml requiredcfgs/
> done

Contents of "requiredcfgs" directory after execution of the above bash script.

$ ls -1 requiredcfgs/
cfg_10_16.xml
cfg_11_5.xml
cfg_11_9.xml
cfg_12_1.xml
cfg_19_1.xml



Rename file to uppercase except extension - Bash:
-----------------------------------------------------------------------

My present working directory got the following files.

$ ls -1
new.py
readme.txt
sl.pl
test.py

Required: Make all file in this directory uppercase but not their extension (ex: image.jpg becoming IMAGE.jpg)

The shell script on the command prompt:

$ ls | while read file
> do
> name=${file%%.*}
> extn=${file##*.}
> newfilename=$(echo $(echo $name | tr 'a-z' 'A-Z').$extn)
> echo "Moving $file to $newfilename"
> mv $file $newfilename
> done

Note: Linux secondary prompt (PS2)
The > (greater than sign) is the Linux secondary prompt (PS2).
When one issues command that is incomplete, the shell will display this prompt and will wait for the user to complete the command and hit Enter again.

Output of the above bash script:

Moving new.py to NEW.py
Moving readme.txt to README.txt
Moving sl.pl to SL.pl
Moving test.py to TEST.py

Now

$ ls -1
NEW.py
README.txt
SL.pl
TEST.py


Bash script for sequential subtraction of numbers :
---------------------------------------------------------------------------

Input file epoch.txt contains UNIX epoch time in the following format.

$ cat epoch.txt
1249887102
1249887121
1249887181
1249887241
1249887433
1249887481
1249887541
1249887601
1249887661

Required:
Subtract 1st number from 2nd number(i.e. 2nd-1st),3rd number minus 2nd number and so on ...

The bash script:

#!/bin/sh

FILE=epoch.txt
N=1
total=$(sed -n '$=' $FILE)
until [ "$N" -eq $total ]
    do
        S1=$N
        ((S2=N+1))
        N=$S2
        VAL1=$(sed -n "$S1 p" $FILE)
        VAL2=$(sed -n "$S2 p" $FILE)
        echo "$VAL2 - $VAL1 = $((VAL2 - VAL1))"
done




$ sh difcal.sh
1249887121 - 1249887102 = 19
1249887181 - 1249887121 = 60
1249887241 - 1249887181 = 60
1249887433 - 1249887241 = 192
1249887481 - 1249887433 = 48
1249887541 - 1249887481 = 60
1249887601 - 1249887541 = 60
1249887661 - 1249887601 = 60


Padding zeros in file name using bash:
-----------------------------------------------------------

Example 1
---------
Files in my current directory are:

$ ls | paste -
a.10.done
a.2.done
m.100.done
n.1.done


Required: Rename the above files to

a.0010.done
a.0002.done
m.0100.done
n.0001.done

i.e. make the "number part" of the filename equal width by padding the number with leading zeroes

The solution using awk and bash parameter substitution

for file in $(ls)
 do
   eval $(echo "$file"|awk -F'.' '{print "prefix="$1";sl="$2}')
   mv $file $(printf "%s.%04d.%s\n" "$prefix" "$sl" "done")
   echo "Renamed $file”
done

Example 2
---------
Files in my current directory are:

$ ls | paste -
apl.1
apl.10
apl.100
apl.2
apl.5

Required: Rename the above files to

apl.0001
apl.0010
apl.0100
apl.0002
apl.0005

The solution:

for file in apl.*
 do
   psl='0000'${file#apl.}
   psl=${psl:(-4)}
   mv $file apl.$psl
done


Another way would be:

for file in apl.*
 do
   mv $file $(printf apl.%04d ${file#apl.})
done


Remove a path from $PATH variable in bash:
-------------------------------------------------------------------

$PATH on my ubuntu:

$ echo $PATH
o/p:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/org/bin:/opt/appls


Now to remove any path which contain "/org/" from PATH variable:

$ echo $PATH | tr ':' '\n' | awk '$0 !~ "/org/"' | paste -sd:
o/p:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/appls

Export the above to PATH

$ export PATH=$(echo $PATH | tr ':' '\n' | awk '$0 !~ "/org/"' | paste -sd:)
$ echo $PATH
o/p:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/appls

Use
awk '$0 != "/usr/local/sbin"'
in the above export awk part if you exactly want to remove the path "/usr/local/sbin" from PATH.

And using bash array:

$ echo $PATH
o/p:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/org/bin:/opt/appl

Remove any path which contain "/org/" from PATH

The one liner:
$ PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%*/org/*});IFS=':';echo "${p[*]}";unset IFS)

$ echo $PATH
o/p:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/appl


Parsing env variable PATH in bash:
-----------------------------------------------------

Requirement: Check for the existence of the directories mentioned in PATH env variable.

The bash script:

#!/bin/sh
IFS=:

read dirs <<END
$PATH
END

for dir in $dirs
   do
      [ ! -e "$dir" ] && echo "$dir not exist" \
      || echo "$dir exist"
done

Executing the above bash script:

$ ./check-PATH.sh
/usr/local/bin exist
/usr/bin exist
/bin exist
/usr/X11R6/bin exist
/cygdrive/c/WINDOWS/system32 exist
/cygdrive/c/WINDOWS exist
/usr/bin exist
/usr/lib/sanity not exist


Test for empty directory - bash scripting:
------------------------------------------------------------

Lets see how we can check whether a directory is empty or not in bash scripting.

#Create a directory
$ mkdir testdir

#Usual way to check if a directory is empty or not
$ [ -z $(ls testdir/) ] && echo "empty" || echo "NOT"
empty

#Lets create a file in testdir
$ touch testdir/file1

#Oh, it works, great
$ [ -z $(ls testdir/) ] && echo "empty" || echo "NOT"
NOT

#Create one more file, file2
$ touch testdir/file2

#Now see, ohh!!! it has faced some problem
$ [ -z $(ls testdir/) ] && echo "empty" || echo "NOT"
-bash: [: file1: binary operator expected
NOT

#Solution to the above:
$ [ -z "$(ls testdir/)" ] && echo "empty" || echo "NOT"
NOT

#Clear the testdir
$ rm testdir/file*

#So that
$ [ -z "$(ls testdir/)" ] && echo "empty" || echo "NOT"
empty

#Create a hidden file in the testdir
$ touch testdir/.hden

#But the above test condition is not going to take account of hidden files (as we are doing only ls)
$ [ -z "$(ls testdir/)" ] && echo "empty" || echo "NOT"
empty

#So use -A (do not list implied . and ..) option with ls
$ [ -z "$(ls -A testdir/)" ] && echo "empty" || echo "NOT"
NOT

#Create a file
$ touch testdir/file1

#Working ...
$ [ -z "$(ls -A testdir/)" ] && echo "empty" || echo "NOT"
NOT

#Remove the files (including hidden file) from testdir
$ rm testdir/.hden ; rm testdir/file1

#So the above is working.
$ [ -z "$(ls -A testdir/)" ] && echo "empty" || echo "NOT"
empty

But if we use -a option instead of -A
$ [ -z "$(ls -a testdir/)" ] && echo "empty" || echo "NOT"
NOT

This is because:

$ ls -a testdir/
. ..

But
$ ls -A testdir/


Handling argument list too long - bash :
--------------------------------------------------------------


I have nearly 200,000 files in one of my log directory out of which number of files of the name format "ka.log.*" is 120,000. So whenever I try to do apply some command such as rm, ls or cp etc on those big set of "ka.log.*" files, I used to get

$ ls ka.log.*
bash: /bin/ls: Argument list too long

$ cp ka.log.* new/
bash: /bin/cp: Argument list too long

$ mv ka.log.* new/
bash: /bin/mv: Argument list too long

$ rm ka.log.*
bash: /bin/rm: Argument list too long

"Argument list too long" error for the above commands is due to the limitation of the command (rm, mv, ls, cp) to handle large number of files(arguments).

Linux 'find' command is useful to perform these operations (ls, cp, mv or rm etc) on such big set of files/arguments.

e.g.

To copy those "ka.log.*" to directory /somedir

$ find . -name "ka.log.*" -exec cp {} /somedir/ \;

Looping through while:

find .  -name "ka.log.*" | while read FILE
    do
    ...
    <some operation on $FILE>
    ...
done


Another way is to assign the file names to a variable, e.g.

FILES=$(echo /mydir/ka.log.*)

for FILE in $FILES
    do
    ...
    <some operation on $FILE>
    …
done.