stream_command_output Function

public function stream_command_output(command, output, exit_status) result(success)

Stream command output directly to memory.

Executes the given command via popen() and streams output directly to an allocatable string, eliminating disk I/O.

@param[in] command The shell command to execute @param[out] output The command output (allocated) @param[out] exit_status Exit status of the command @return .true. if successful, .false. on error

Example

character(len=:), allocatable :: output
integer :: status
logical :: success

success = stream_command_output('curl -s https://example.com', output, status)
if (success .and. status == 0) then
    print *, 'Output: ', output
end if

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: command
character(len=:), intent(out), allocatable :: output
integer, intent(out) :: exit_status

Return Value logical


Source Code

    function stream_command_output(command, output, exit_status) result(success)
        character(len=*), intent(in) :: command
        character(len=:), allocatable, intent(out) :: output
        integer, intent(out) :: exit_status
        logical :: success

        type(c_ptr) :: pipe
        character(len=:), allocatable :: c_command
        character(len=CHUNK_SIZE), target :: buffer
        integer(c_size_t) :: bytes_read
        character(len=:), allocatable :: temp_output
        integer(c_int) :: close_status
        integer :: total_bytes
        character(len=256) :: msg

        success = .false.
        exit_status = -1
        total_bytes = 0

        ! Check if streaming is available
        if (.not. is_streaming_available()) then
            call s3_log_debug('Streaming not available on this platform')
            return
        end if

        call s3_log_trace('Streaming command: ' // trim(command))

        ! Convert Fortran string to C string (null-terminated)
        c_command = trim(command) // C_NULL_CHAR

        ! Open pipe for reading
        call s3_log_debug('Opening pipe with popen()')
        pipe = c_popen(c_command, 'r' // C_NULL_CHAR)
        if (.not. c_associated(pipe)) then
            call s3_log_error('popen() failed - pipe not associated')
            return
        end if

        call s3_log_debug('Pipe opened successfully, starting to read')

        ! Initialize output
        output = ''

        ! Read data in chunks
        do
            bytes_read = c_fread(c_loc(buffer), 1_c_size_t, &
                                 int(CHUNK_SIZE, c_size_t), pipe)

            ! Check if we've reached end of stream or error
            if (bytes_read <= 0) exit

            total_bytes = total_bytes + int(bytes_read)
            write(msg, '(A,I0,A,I0,A)') 'Read ', int(bytes_read), ' bytes (total: ', total_bytes, ')'
            call s3_log_trace(trim(msg))

            ! Append chunk to output
            if (allocated(temp_output)) deallocate(temp_output)
            if (len(output) > 0) then
                allocate(character(len=len(output) + int(bytes_read)) :: temp_output)
                temp_output = output // buffer(1:bytes_read)
            else
                allocate(character(len=int(bytes_read)) :: temp_output)
                temp_output = buffer(1:bytes_read)
            end if

            ! Move temp to output
            call move_alloc(temp_output, output)
        end do

        write(msg, '(A,I0,A)') 'Finished reading ', total_bytes, ' total bytes'
        call s3_log_debug(trim(msg))

        ! Close pipe and get exit status
        close_status = c_pclose(pipe)
        write(msg, '(A,I0)') 'pclose() returned: ', close_status
        call s3_log_debug(trim(msg))

        ! pclose returns exit status in platform-dependent way
        ! On most systems, need to check using WEXITSTATUS-like logic
        ! For simplicity, treat 0 as success, non-zero as failure
        if (close_status == 0) then
            exit_status = 0
            success = .true.
            call s3_log_debug('Stream command completed successfully')
        else
            ! Command failed, but we may still have partial output
            exit_status = close_status
            success = .false.
            write(msg, '(A,I0,A,I0,A)') 'Stream command failed with exit status ', &
                close_status, ' (bytes read: ', total_bytes, ')'
            call s3_log_error(trim(msg))
        end if

    end function stream_command_output