3

This question is kind of related to my previous question.

This is the output of tree:

[xyz@localhost Semester1]$ tree
.
├── Eng
│   ├── credits
│   ├── links
│   └── notes
├── IT
│   ├── credits
│   ├── links
│   └── notes
├── IT_workshop
│   ├── credits
│   ├── links
│   └── notes
├── LA
│   ├── credits
│   ├── links
│   └── notes
├── OS
│   ├── credits
│   ├── links
│   └── notes
├── OS_lab
│   ├── credits
│   ├── links
│   └── notes
├── Psy
│   ├── credits
│   ├── links
│   └── notes
├── Python
│   ├── credits
│   ├── links
│   └── notes
└── Python_lab
    ├── credits
    ├── links
    └── notes

9 directories, 27 files

Now I want to mention the number of credits each course is having in the credits file of the respective folder using cat.

For example, this is the number of credits each course is having:

Course name Credits
Eng 2
LA 3
Python 3
OS 3
IT 3
IT_workshop 1
Python_lab 1
OS_lab 1
Psy 2

For adding credits details in Eng/credits, I need to run this:

cat >> Eng/credits

and type the number of credits (i.e., 2) and then press Ctrl+D.

I have to do the same step for the remaining 8 files and it seems like tedious work. Is there any way to do all of this at once using cat?

I spoke to some friends who are knowledgeable about Bash, and they said that it is better to use echo or printf in my case. Why is that so?


I'm expecting to do this:

  • In the Eng/credits file, I want the number 2 to be written.
  • In the LA/credits file, I want the number 3 to be written.
  • In the Python/credits file, I want the number 3 to be written.
  • In the OS/credits file, I want the number 3 to be written.
  • In the IT/credits file, I want the number 3 to be written.
  • In the IT_workshop/credits file, I want the number 1 to be written.
  • In the Python_lab/credits file, I want the number 1 to be written.
  • In the OS_lab/credits file, I want the number 1 to be written.
  • In the Psy/credits file, I want the number 2 to be written.

After reading Hack Saw's answer, I tried this command:

echo {2,3,3,3,3,1,1,1,2} >> {Eng/credits,LA/credits,Python/credits,OS/credits,IT/credits,IT_workshop/credits,Python_lab/credits,OS_lab/credits,Psy/credits}

but it didn't work as I expected it to:

bash: {Eng/credits,LA/credits,Python/credits,OS/credits,IT/credits,IT_workshop/credits,Python_lab/credits,OS_lab/credits,Psy/credits}: ambiguous redirect
  • 2
    Is pure cat a necessity? If you already have the data in a CSV-like format, many other tools would be much more handy. – FelixJN Nov 05 '22 at 15:42
  • @FelixJN This question was actually there in my assignment and they asked me to use cat. But I would love to see people solve this issue using other commands. I didn't store the data in CSV-like format, but if it's needed, I'm willing to do it. – Random Person Nov 05 '22 at 15:56

2 Answers2

3

Cat is used to output whole files.  Its operands (non-option parameters) are files.  When you do cat >> foo, you are redirecting the file stdout to foo.

Echo and printf take immediate data.

echo 2 >> foo

... puts a 2 into the file foo.

Printf gives you a way to add extra formatting onto things, is more standardized, and is potentially safer in the face of unknown data.

Hack Saw
  • 1,024
  • It's parameters are files technically speaking cat may receive non-file options such as -v or --help – phuclv Nov 06 '22 at 05:22
2

You probably won't be able to do it with cat with any method less tedious than the manual method you show in the question.  Try this:

while read course credit; do
  echo $credit >> $course/credits
done <<EOF
Eng 2
LA 3
Python 3
OS 3
IT 3
IT_workshop 1
Python_lab 1
OS_lab 1
Psy 2
EOF

From the bash man page:

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]

    One line is read from the standard input, or from the file descriptor fd supplied as an argument to the -u option, split into words as described above under Word Splitting, and the first word is assigned to the first name, the second word to the second name, and so on.  If there are more words than names, the remaining words and their intervening delimiters are assigned to the last name.  If there are fewer words read from the input stream than names, the remaining names are assigned empty values.  The characters in IFS are used to split the line into words using the same rules the shell uses for expansion (described above under Word Splitting). The backslash character (\) may be used to remove any special meaning for the next character read and for line continuation.  Options, if supplied, have the following meanings:
            ︙

The read course credit command will assign the first word on each line to the variable course and then the rest of the line (a number in this case) gets assigned to the variable credit.

Also from the man page:

Here Documents

    This type of redirection instructs the shell to read input from the current source until a line containing only delimiter (with no trailing blanks) is seen.  All of the lines read up to that point are then used as the standard input (or file descriptor n if n is specified) for a command.

    The format of here-documents is:

[n]<<[-]word
        here-document
delimiter

In this case 'word' and 'delimiter' are EOF and the 'here-document' is the data. You can replace everything from <<EOF onwards with a file containing the data and redirect stdin from that; e.g., <data.txt

Paranoid
  • 315