3

I want to add content to a beginning of each file in current directory. I need to learn how to take all file names from current working directory at once and put them as arguments; I don't need to do this by specifying every file manually as an argument to the script. I want to use this new knowledge for further scripts. I don't need sideways or different solutions. I need to learn how to take command ( ls ) output and put it as an argument. I tried and failed to do that with:

./file-edit.sh $(ls)
./file-edit.sh `ls`

This is my script which works:

#!/bin/bash
lineno=2            # Append from line 2 
BAD_ARGS=85         # Error code

string0="###################################################################"
string2="#Description    :"
string3="#Date           :`date +%d-%B-%Y`"
string4="#Author         :Milos Stojanovic"
string5="#Contact:       :https://www.linkedin.com/in/xxx/"
string6="###################################################################"
# Can be any other strings

if [ ! -f $1 ]; then
        echo "Please specify at least 1 filename as an argument!"
        exit $BAD_ARGS
fi

until [ -z  "$1" ]
do
        string1="#Script Name    :$1"
        file=$1;
        sed -i ""$lineno"i $string0\n$string1\n$string2\n$string3\n$string4\n$string5\n$string6" $file
        echo "File "\'$file\'" altered at line number: $lineno"
        shift 1
done

exit 0
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

3 Answers3

6

To process each argument separately in your script, use a loop in your like so:

for pathname do

    # Insert code here that uses "$pathname".
    # You will not need to shift arguments.

done

To call your script with all the names in the current directory, use

./script.sh ./*

The shell will expand the ./* pattern to a list containing all names in the current directory.

This has the advantage over using ls that it does not use ls and will therefore cope with filenames that contains any valid characters, even spaces, tabs and newlines.

Or, you can move the filename globbing into the script itself (to avoid having to give the script any arguments on the command line) with

for pathname in ./*; do
    # Insert code here that uses "$pathname".
done

If your script needs to distinguish between names that refer to regular files and names that refer to anything that is not a regular file (e.g. directories), use the -f test in your loop:

# skip non-regular files
[ ! -f "$pathname" ] && continue

This would still let though symbolic links to regular files. To skip these too, change the test into

if [ ! -f "$pathname" ] || [ -L "$pathname" ]; then
    # Skip non-regular files and symbolic links.
    continue
fi

Other comments:

You can assign a multi-line string to a variable by including newlines in the string:

boilerplate='Hello and welcome!
This is my program.
Enjoy'

To insert a text after line two in a text document using sed (using -i here for in-place editing):

printf '%s\n' "$boilerplate" | sed -i '2r /dev/stdin' somefile

or, with a here-document,

sed -i '2r /dev/stdin' somefile <<END_BOILERPLATE
Hello and welcome!
This is my program.
Enjoy
END_BOILERPLATE
Kusalananda
  • 333,661
3

Since the other answers don't address what specifically you did wrong,

I tried and failed to do that with:

./file-edit.sh $(ls)

I tried your script and I got:

$ ./file-edit.sh $(ls)
Please specify at least 1 filename as an argument!

On checking the output of ls, I noticed that it lists directories first. If I moved to a directory without subdirectories, I got:

$ ./file-edit.sh $(ls)
File 'a' altered at line number: 2
File 'b' altered at line number: 2
File 'c' altered at line number: 2 

So, the way you're checking your arguments is the problem. -f only succeeds on regular files. It saw a directory was provided for the first argument and gave an error saying that the user didn't provide any filename arguments, even when they did.

If you want to make sure that at least one argument was provided, use $# which expands to the number of arguments provided:

if [ "$#" -eq 0 ]; then
  echo "Please specify at least 1 filename as an argument!"
  ...

If you want to make sure they're regular files, then do the check in the loop, and check each one, not just the first.

until [ -z  "$1" ]
do
        string1="#Script Name    :$1"
        file=$1;
        if [ -f "$file" ]; then
                sed -i "${lineno}i $string0\n$string1\n$string2\n$string3\n$string4\n$string5\n$string6" "$file"
                echo "File '$file' altered at line number: $lineno"
        fi
        shift 1
done

Now, that code is in case you want to ignore non-regular-file arguments, but you can just as easily change it to quit on seeing a non-regular-file if that's what you want.

That being said, it's not advised to use the output of ls like that. There can be filenames with spaces and bash will just split everything on whitespace. If you have a file foo bar.txt, bash will split that and your script will look for files foo and bar.txt.

From your comment on your question, I see someone must've suggested you use a glob instead. I don't really understand your point about it not working. ./file-edit.sh * works just fine for me.

JoL
  • 4,735
2

I would have gone with something like:

find <dir> -type f -print0 | xargs -0 sed -i '...'

instead of the whole until loop. Should be faster a bunch too.

The reasons I posted an alternative:

  • Thou shall not parseth ls output (600+ votes)

  • I'd rather have one fast line in my script, using system mechanisms, than hack my own (possibly buggy) multi-line loop construct.

  • Most self-built constructs will have problem with either "files with spaces", or run into problems with argument length.

  • Hello, thank you for suggestion. But i m not looking for some other way to do this. They are for sure many other solutions how else this could be done. Why i posted this question is because I want to learn, how to pass an output from a command as an argument to a script. Thank you! – Miloš Stojanović Apr 26 '20 at 15:35
  • @MilošStojanović proposing an alternative using -print0 | xargs was to help prevent yourself from shooting yourself in the foot later. But O.K., i also had to shoot myself in the foot once before i stopped parsing ls output myself, and looked up xargsusage.. – Alex Stragies Apr 26 '20 at 16:25
  • Will test it with xargs also, thank you @AlexStragies – Miloš Stojanović Apr 26 '20 at 16:27
  • @MilošStojanović Solutions without xargs will fail in folders with either many files, and/or very long names (= "problems with argument length"). Fun to debug ... not! – Alex Stragies Apr 26 '20 at 16:32