简体   繁体   中英

Passing arguments through bash script to C program

I'm trying to write a program that accept file names as arguments in a bash script, then passes them to a C program that replaces spaces with underscores, then the bash script uses that to rename the file.

For example, the input would be

Bash bash_script "test test test.txt"

and the file would be renamed test_test_test.txt .

My problem is that when I run this, it tells me that I'm using mv incorrectly. Why is this? I'm new to bash, so I'm sorry for using program/script incorrectly.

My C program:

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;
    char * file = argv[1];

    while(i<=256){  //max file size is 256 characters on mac
        if (argc != 2)
            printf("Please provide one file name.");
        else if(file[i] == ' ')
            file[i] = '_';
        i++;
    }
    printf("%s \n", file);
    return 0;
}

My Bash program:

#! /bin/bash

VAR = "C_program '$@'"
mv $1 $VAR

This line:

 VAR = "C_program '$@'" 

doesn't do what you want. And your mv line is broken too.

VAR=$(C_program "$@")
mv "$1" "$VAR"

Also, your C program doesn't exit with an error when an error is detected.

Also, sed and tr are existing programs that are suitable alternatives to writing your C program to transliterate ( translate ) characters in strings.

Also, rename / prename are existing (Perl) programs that handle rename files with regular expression pattern functionality to rename files, which may be already available on your system(s).

In your specific example, I would not shell out to a custom C program to do this.

Here's an equivalent shell script (not requiring tr , sed or any programs besides bash and mv ):

mv "$1" "${1// /_}"

In your specific problem, you are not setting your VAR properly. Shell scripts cannot accept spaces around the = when setting variables, and you need to use backticks or $() to execute an external program. So, properly written, you want

VAR="$(C_program "$@")"

2 problems with the mv $1 "C_program '$@'"

  • $1 needs double quotes -> "$1"
  • "C_program '$@'" should be `C_program '$1'` or $(C_program '$@')

however this can be done more efficiently with

IFS="
"
for x in $@; do
    mv "$x" "${x// /_}"
done

If you just want the results than I would suggest simply replace the bash script and custom C program with a single short Bourne or POSIX shell script.

#!/bin/sh
NEW_FILE_NAME= `echo $1 | tr ' ' _  `
mv $1 $NEW_FILE_NAME

Otherwise

You want the shell script to run your C program (I'll refer to it as todash for simplicity) before setting the shell variable VAR . This is done using the backtick ` (located near upper right corner of US keyboards with tilde, '~') operation.

#!/bin/sh
VAR= `todash $1`
mv $1 $VAR

For todash.c I'll suggest a couple of mostly small improvements.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int main(int argc, char *argv[])
{
   char * filename;  
   /* Program operates on filename, not file or its contents, so use a variable
      name that reflect that. Good variable names make debugging easier.
    */

   if (argc != 2) {
     printf("Please provide one file name.");
     return EXIT_FAILURE;  /* or exit(EXIT_FAILURE); */
   } else {
     /* Only set filename, once known that argv[1] is not NULL (empty) */
     filename = argv[1];
   }

   /* Use a simple for loop
    * PATH_MAX is a standard system constant included with limits.h
    */
   for (i = 0; (i < PATH_MAX) && (filename[i] != '\0'); i++) {
      if (filename[i] == ' ') {
         filename[i] = '_';
      }
   }
   printf("%s \n", filename);
   return EXIT_SUCCESS;
}

The added length is only my additional inline comments.

The largest change was untangling the argc if comparison from the while loop, which once simplified, become a classic example of where to use a for loop.

And for your sanity, and those around you, use braces (curly brackets) around conditional blocks of code. Just because you are allowed to not include them, does not mean you should (not include them). Programs tend to live beyond their original intention and expand in the future. Avoid making mistakes later by including them now.

I for the most part second mctylr, but recommend an even cleaner version of the loop:

for ( ; *file != '\0'; file++ ) {
    if ( *file == ' ' ) *file = '_';
}

Such is the power of array pointers. Now you save ~4 bytes and have some pretty slick code to boot.

This is far from being just a facelift though. Your code is actually very dangerous because if 'argv[1]' is less than 256 characters, you are reading arbitrary memory past the array bounds and potentially writing to it (in a hypothetical scenario where filepath is of length 0, the next 255 whitespaces in memory are going to be changed to underscores.)

Instead, check whether this equates to true:

if ( strlen ( argv[1] ) > 256 )

... and exiting with an error if it does. Then read argv[1].

Please correct me if I got anything wrong, I'm by no means an expert.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM